aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharles Kerr <charles.kerr@canonical.com>2014-04-24 22:43:15 -0500
committerCharles Kerr <charles.kerr@canonical.com>2014-04-24 22:43:15 -0500
commit27b500307fe8035defb510a0d694fa1fa7fa3950 (patch)
treeb5108a91beabf803c4ea4d71a99129718c5a7026
parent4008e0a91ebdc1cf5c5715a122fed318124c8802 (diff)
parenta396e6af3cd16530202f6cbecbd45c7a3f6ac893 (diff)
downloadayatana-indicator-datetime-27b500307fe8035defb510a0d694fa1fa7fa3950.tar.gz
ayatana-indicator-datetime-27b500307fe8035defb510a0d694fa1fa7fa3950.tar.bz2
ayatana-indicator-datetime-27b500307fe8035defb510a0d694fa1fa7fa3950.zip
prefer to use ubuntu-platform-hardware-api for wakeups when possible s.t. user-defined alarms/appointments can wake up the phone from sleep to give a snap decision.
-rw-r--r--CMakeLists.txt1
-rw-r--r--include/datetime/alarm-queue-simple.h63
-rw-r--r--include/datetime/alarm-queue.h (renamed from include/datetime/clock-watcher.h)40
-rw-r--r--include/datetime/date-time.h3
-rw-r--r--include/datetime/planner-range.h2
-rw-r--r--include/datetime/wakeup-timer-mainloop.h62
-rw-r--r--include/datetime/wakeup-timer-uha.h64
-rw-r--r--include/datetime/wakeup-timer.h55
-rw-r--r--src/CMakeLists.txt6
-rw-r--r--src/alarm-queue-simple.cpp153
-rw-r--r--src/clock-watcher.cpp81
-rw-r--r--src/date-time.cpp5
-rw-r--r--src/main.cpp110
-rw-r--r--src/planner-range.cpp2
-rw-r--r--src/wakeup-timer-mainloop.cpp136
-rw-r--r--src/wakeup-timer-uha.cpp167
-rw-r--r--tests/CMakeLists.txt2
-rw-r--r--tests/test-alarm-queue.cpp (renamed from tests/test-clock-watcher.cpp)32
18 files changed, 829 insertions, 155 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 378dc03..9e068f4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,6 +42,7 @@ pkg_check_modules (SERVICE_DEPS REQUIRED
url-dispatcher-1>=1
properties-cpp>=0.0.1)
include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
+set (SERVICE_DEPS_LIBRARIES -lubuntu_platform_hardware_api ${SERVICE_DEPS_LIBRARIES})
##
## custom targets
diff --git a/include/datetime/alarm-queue-simple.h b/include/datetime/alarm-queue-simple.h
new file mode 100644
index 0000000..8461227
--- /dev/null
+++ b/include/datetime/alarm-queue-simple.h
@@ -0,0 +1,63 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H
+#define INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H
+
+#include <datetime/alarm-queue.h>
+#include <datetime/clock.h>
+#include <datetime/planner.h>
+#include <datetime/wakeup-timer.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/**
+ * \brief A #AlarmQueue implementation
+ */
+class SimpleAlarmQueue: public AlarmQueue
+{
+public:
+ SimpleAlarmQueue(const std::shared_ptr<Clock>& clock,
+ const std::shared_ptr<Planner>& upcoming_planner,
+ const std::shared_ptr<WakeupTimer>& timer);
+ ~SimpleAlarmQueue();
+ core::Signal<const Appointment&>& alarm_reached();
+
+private:
+ void requeue();
+ bool find_next_alarm(Appointment& setme) const;
+ std::vector<Appointment> find_current_alarms() const;
+ void check_alarms();
+
+ std::set<std::string> m_triggered;
+ const std::shared_ptr<Clock> m_clock;
+ const std::shared_ptr<Planner> m_planner;
+ const std::shared_ptr<WakeupTimer> m_timer;
+ core::Signal<const Appointment&> m_alarm_reached;
+ DateTime m_time;
+};
+
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_ALARM_QUEUE_H
diff --git a/include/datetime/clock-watcher.h b/include/datetime/alarm-queue.h
index 90bbb63..ea51957 100644
--- a/include/datetime/clock-watcher.h
+++ b/include/datetime/alarm-queue.h
@@ -17,12 +17,10 @@
* Charles Kerr <charles.kerr@canonical.com>
*/
-#ifndef INDICATOR_DATETIME_CLOCK_WATCHER_H
-#define INDICATOR_DATETIME_CLOCK_WATCHER_H
+#ifndef INDICATOR_DATETIME_ALARM_QUEUE_H
+#define INDICATOR_DATETIME_ALARM_QUEUE_H
#include <datetime/appointment.h>
-#include <datetime/clock.h>
-#include <datetime/planner-upcoming.h>
#include <core/signal.h>
@@ -34,42 +32,28 @@ namespace unity {
namespace indicator {
namespace datetime {
+/***
+****
+***/
/**
* \brief Watches the clock and appointments to notify when an
* appointment's time is reached.
*/
-class ClockWatcher
+class AlarmQueue
{
public:
- ClockWatcher() =default;
- virtual ~ClockWatcher() =default;
+ AlarmQueue() =default;
+ virtual ~AlarmQueue() =default;
virtual core::Signal<const Appointment&>& alarm_reached() = 0;
};
-
-/**
- * \brief A #ClockWatcher implementation
- */
-class ClockWatcherImpl: public ClockWatcher
-{
-public:
- ClockWatcherImpl(const std::shared_ptr<Clock>& clock,
- const std::shared_ptr<UpcomingPlanner>& upcoming_planner);
- ~ClockWatcherImpl() =default;
- core::Signal<const Appointment&>& alarm_reached();
-
-private:
- void pulse();
- std::set<std::string> m_triggered;
- const std::shared_ptr<Clock> m_clock;
- const std::shared_ptr<UpcomingPlanner> m_upcoming_planner;
- core::Signal<const Appointment&> m_alarm_reached;
-};
-
+/***
+****
+***/
} // namespace datetime
} // namespace indicator
} // namespace unity
-#endif // INDICATOR_DATETIME_CLOCK_WATCHER_H
+#endif // INDICATOR_DATETIME_ALARM_QUEUE_H
diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h
index f861c2e..490ed40 100644
--- a/include/datetime/date-time.h
+++ b/include/datetime/date-time.h
@@ -61,10 +61,13 @@ public:
bool operator<=(const DateTime& that) const;
bool operator!=(const DateTime& that) const;
bool operator==(const DateTime& that) const;
+ int64_t operator- (const DateTime& that) const;
static bool is_same_day(const DateTime& a, const DateTime& b);
static bool is_same_minute(const DateTime& a, const DateTime& b);
+ bool is_set() const { return m_dt.get() != nullptr; }
+
private:
std::shared_ptr<GDateTime> m_dt;
};
diff --git a/include/datetime/planner-range.h b/include/datetime/planner-range.h
index 25334a6..2ee2fa0 100644
--- a/include/datetime/planner-range.h
+++ b/include/datetime/planner-range.h
@@ -53,7 +53,7 @@ class SimpleRangePlanner: public RangePlanner
{
public:
SimpleRangePlanner(const std::shared_ptr<Engine>& engine,
- const std::shared_ptr<Timezone>& timezone);
+ const std::shared_ptr<Timezone>& timezone);
virtual ~SimpleRangePlanner();
core::Property<std::vector<Appointment>>& appointments();
diff --git a/include/datetime/wakeup-timer-mainloop.h b/include/datetime/wakeup-timer-mainloop.h
new file mode 100644
index 0000000..480d728
--- /dev/null
+++ b/include/datetime/wakeup-timer-mainloop.h
@@ -0,0 +1,62 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_WAKEUP_TIMER_MAINLOOP_H
+#define INDICATOR_DATETIME_WAKEUP_TIMER_MAINLOOP_H
+
+#include <datetime/clock.h>
+#include <datetime/wakeup-timer.h>
+
+#include <memory> // std::unique_ptr, std::shared_ptr
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+/**
+ * \brief a WakeupTimer implemented with g_timeout_add()
+ */
+class MainloopWakeupTimer: public WakeupTimer
+{
+public:
+ MainloopWakeupTimer(const std::shared_ptr<Clock>&);
+ ~MainloopWakeupTimer();
+ void set_wakeup_time (const DateTime&);
+ core::Signal<>& timeout();
+
+private:
+ MainloopWakeupTimer(const MainloopWakeupTimer&) =delete;
+ MainloopWakeupTimer& operator= (const MainloopWakeupTimer&) =delete;
+ class Impl;
+ std::unique_ptr<Impl> p;
+};
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_WAKEUP_TIMER_MAINLOOP_H
diff --git a/include/datetime/wakeup-timer-uha.h b/include/datetime/wakeup-timer-uha.h
new file mode 100644
index 0000000..093548b
--- /dev/null
+++ b/include/datetime/wakeup-timer-uha.h
@@ -0,0 +1,64 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_WAKEUP_TIMER_UHA_H
+#define INDICATOR_DATETIME_WAKEUP_TIMER_UHA_H
+
+#include <datetime/clock.h>
+#include <datetime/wakeup-timer.h>
+
+#include <memory> // std::unique_ptr, std::shared_ptr
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+/**
+ * \brief a WakeupTimer implemented the UbuntuHardwareAlarm API
+ */
+class UhaWakeupTimer: public WakeupTimer
+{
+public:
+ UhaWakeupTimer(const std::shared_ptr<Clock>&);
+ ~UhaWakeupTimer();
+ void set_wakeup_time (const DateTime&);
+ core::Signal<>& timeout();
+
+ static bool is_supported();
+
+private:
+ UhaWakeupTimer(const UhaWakeupTimer&) =delete;
+ UhaWakeupTimer& operator= (const UhaWakeupTimer&) =delete;
+ class Impl;
+ std::unique_ptr<Impl> p;
+};
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_WAKEUP_TIMER_UHA_H
diff --git a/include/datetime/wakeup-timer.h b/include/datetime/wakeup-timer.h
new file mode 100644
index 0000000..0a9923c
--- /dev/null
+++ b/include/datetime/wakeup-timer.h
@@ -0,0 +1,55 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_WAKEUP_TIMER_H
+#define INDICATOR_DATETIME_WAKEUP_TIMER_H
+
+#include <datetime/date-time.h>
+
+#include <core/signal.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+/**
+ * \brief A one-shot timer that emits a signal when its timeout is reached.
+ */
+class WakeupTimer
+{
+public:
+ WakeupTimer() =default;
+ virtual ~WakeupTimer() =default;
+ virtual void set_wakeup_time (const DateTime&) =0;
+ virtual core::Signal<>& timeout() = 0;
+};
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_WAKEUP_TIMER_H
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9bc22f2..924e538 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -10,10 +10,10 @@ add_definitions (-DTIMEZONE_FILE="/etc/timezone"
add_library (${SERVICE_LIB} STATIC
actions.cpp
actions-live.cpp
+ alarm-queue-simple.cpp
appointment.cpp
clock.cpp
clock-live.cpp
- clock-watcher.cpp
date-time.cpp
engine-eds.cpp
exporter.cpp
@@ -30,7 +30,9 @@ add_library (${SERVICE_LIB} STATIC
timezone-file.cpp
timezone-geoclue.cpp
timezones-live.cpp
- utils.c)
+ utils.c
+ wakeup-timer-uha.cpp
+ wakeup-timer-mainloop.cpp)
include_directories (${CMAKE_SOURCE_DIR})
link_directories (${SERVICE_DEPS_LIBRARY_DIRS})
diff --git a/src/alarm-queue-simple.cpp b/src/alarm-queue-simple.cpp
new file mode 100644
index 0000000..1c871bf
--- /dev/null
+++ b/src/alarm-queue-simple.cpp
@@ -0,0 +1,153 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/alarm-queue-simple.h>
+
+#include <cmath>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+**** Public API
+***/
+
+SimpleAlarmQueue::SimpleAlarmQueue(const std::shared_ptr<Clock>& clock,
+ const std::shared_ptr<Planner>& planner,
+ const std::shared_ptr<WakeupTimer>& timer):
+ m_clock(clock),
+ m_planner(planner),
+ m_timer(timer),
+ m_time(clock->localtime())
+{
+ m_planner->appointments().changed().connect([this](const std::vector<Appointment>&){
+ g_debug("AlarmQueue %p calling requeue() due to appointments changed", this);
+ requeue();
+ });
+
+ m_clock->minute_changed.connect([=]{
+ 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_time) > skew_threshold_usec;
+ m_time = 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();
+}
+
+SimpleAlarmQueue::~SimpleAlarmQueue()
+{
+}
+
+core::Signal<const Appointment&>& SimpleAlarmQueue::alarm_reached()
+{
+ return m_alarm_reached;
+}
+
+/***
+****
+***/
+
+void SimpleAlarmQueue::requeue()
+{
+ // kick any current alarms
+ for (auto current : find_current_alarms())
+ {
+ m_triggered.insert(current.uid);
+ m_alarm_reached(current);
+ }
+
+ // idle until the next alarm
+ Appointment next;
+ if (find_next_alarm(next))
+ {
+ g_debug ("setting timer to wake up for next appointment '%s' at %s",
+ next.summary.c_str(),
+ next.begin.format("%F %T").c_str());
+
+ m_timer->set_wakeup_time(next.begin);
+ }
+}
+
+// find the next alarm that will kick now or in the future
+bool SimpleAlarmQueue::find_next_alarm(Appointment& setme) const
+{
+ bool found = false;
+ Appointment tmp;
+ const auto now = m_clock->localtime();
+ const auto beginning_of_minute = now.add_full (0, 0, 0, 0, 0, -now.seconds());
+
+ for(const auto& walk : m_planner->appointments().get())
+ {
+ if (m_triggered.count(walk.uid)) // did we already use this one?
+ continue;
+
+ if (walk.begin < beginning_of_minute) // has this one already passed?
+ continue;
+
+ if (found && (tmp.begin < walk.begin)) // do we already have a better match?
+ continue;
+
+ tmp = walk;
+ found = true;
+ }
+
+ if (found)
+ setme = tmp;
+
+ return found;
+}
+
+// find the alarm(s) that should kick right now
+std::vector<Appointment> SimpleAlarmQueue::find_current_alarms() const
+{
+ std::vector<Appointment> appointments;
+
+ const auto now = m_clock->localtime();
+
+ for(const auto& walk : m_planner->appointments().get())
+ {
+ if (m_triggered.count(walk.uid)) // did we already use this one?
+ continue;
+ if (!DateTime::is_same_minute(now, walk.begin))
+ continue;
+
+ appointments.push_back(walk);
+ }
+
+ return appointments;
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/src/clock-watcher.cpp b/src/clock-watcher.cpp
deleted file mode 100644
index 5da66c8..0000000
--- a/src/clock-watcher.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Charles Kerr <charles.kerr@canonical.com>
- */
-
-#include <datetime/clock-watcher.h>
-
-namespace unity {
-namespace indicator {
-namespace datetime {
-
-/***
-****
-***/
-
-ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr<Clock>& clock,
- const std::shared_ptr<UpcomingPlanner>& upcoming_planner):
- m_clock(clock),
- m_upcoming_planner(upcoming_planner)
-{
- m_clock->date_changed.connect([this](){
- const auto now = m_clock->localtime();
- g_debug("ClockWatcher %p refretching appointments due to date change: %s", this, now.format("%F %T").c_str());
- m_upcoming_planner->date().set(now);
- });
-
- m_clock->minute_changed.connect([this](){
- g_debug("ClockWatcher %p calling pulse() due to clock minute_changed", this);
- pulse();
- });
-
- m_upcoming_planner->appointments().changed().connect([this](const std::vector<Appointment>&){
- g_debug("ClockWatcher %p calling pulse() due to appointments changed", this);
- pulse();
- });
-
- pulse();
-}
-
-core::Signal<const Appointment&>& ClockWatcherImpl::alarm_reached()
-{
- return m_alarm_reached;
-}
-
-void ClockWatcherImpl::pulse()
-{
- const auto now = m_clock->localtime();
-
- for(const auto& appointment : m_upcoming_planner->appointments().get())
- {
- if (m_triggered.count(appointment.uid))
- continue;
- if (!DateTime::is_same_minute(now, appointment.begin))
- continue;
-
- m_triggered.insert(appointment.uid);
- m_alarm_reached(appointment);
- }
-}
-
-/***
-****
-***/
-
-} // namespace datetime
-} // namespace indicator
-} // namespace unity
diff --git a/src/date-time.cpp b/src/date-time.cpp
index a1c1d1b..a139ea9 100644
--- a/src/date-time.cpp
+++ b/src/date-time.cpp
@@ -174,6 +174,11 @@ bool DateTime::operator==(const DateTime& that) const
return g_date_time_compare(get(), that.get()) == 0;
}
+int64_t DateTime::operator- (const DateTime& that) const
+{
+ return g_date_time_difference(get(), that.get());
+}
+
bool DateTime::is_same_day(const DateTime& a, const DateTime& b)
{
// it's meaningless to compare uninitialized dates
diff --git a/src/main.cpp b/src/main.cpp
index c7b35e5..e35c5da 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -18,8 +18,8 @@
*/
#include <datetime/actions-live.h>
+#include <datetime/alarm-queue-simple.h>
#include <datetime/clock.h>
-#include <datetime/clock-watcher.h>
#include <datetime/engine-mock.h>
#include <datetime/engine-eds.h>
#include <datetime/exporter.h>
@@ -31,6 +31,8 @@
#include <datetime/state.h>
#include <datetime/timezone-file.h>
#include <datetime/timezones-live.h>
+#include <datetime/wakeup-timer-mainloop.h>
+#include <datetime/wakeup-timer-uha.h>
#include <glib/gi18n.h> // bindtextdomain()
#include <gio/gio.h>
@@ -42,6 +44,79 @@
using namespace unity::indicator::datetime;
+namespace
+{
+ std::shared_ptr<Engine> create_engine()
+ {
+ std::shared_ptr<Engine> engine;
+
+ // we don't show appointments in the greeter,
+ // so no need to connect to EDS there...
+ if (!g_strcmp0("lightdm", g_get_user_name()))
+ engine.reset(new MockEngine);
+ else
+ engine.reset(new EdsEngine);
+
+ return engine;
+ }
+
+ std::shared_ptr<WakeupTimer> create_wakeup_timer(const std::shared_ptr<Clock>& clock)
+ {
+ std::shared_ptr<WakeupTimer> wakeup_timer;
+
+ if (UhaWakeupTimer::is_supported()) // prefer to use the platform API
+ wakeup_timer = std::make_shared<UhaWakeupTimer>(clock);
+ else
+ wakeup_timer = std::make_shared<MainloopWakeupTimer>(clock);
+
+ return wakeup_timer;
+ }
+
+ std::shared_ptr<State> create_state(const std::shared_ptr<Engine>& engine,
+ const std::shared_ptr<Timezone>& tz)
+ {
+ // create the live objects
+ auto live_settings = std::make_shared<LiveSettings>();
+ auto live_timezones = std::make_shared<LiveTimezones>(live_settings, TIMEZONE_FILE);
+ auto live_clock = std::make_shared<LiveClock>(live_timezones);
+
+ // create a full-month planner currently pointing to the current month
+ const auto now = live_clock->localtime();
+ auto range_planner = std::make_shared<SimpleRangePlanner>(engine, tz);
+ auto calendar_month = std::make_shared<MonthPlanner>(range_planner, now);
+
+ // create an upcoming-events planner currently pointing to the current date
+ range_planner = std::make_shared<SimpleRangePlanner>(engine, tz);
+ auto calendar_upcoming = std::make_shared<UpcomingPlanner>(range_planner, now);
+
+ // create the state
+ auto state = std::make_shared<State>();
+ state->settings = live_settings;
+ state->clock = live_clock;
+ state->locations = std::make_shared<SettingsLocations>(live_settings, live_timezones);
+ state->calendar_month = calendar_month;
+ state->calendar_upcoming = calendar_upcoming;
+ return state;
+ }
+
+ std::shared_ptr<AlarmQueue> create_simple_alarm_queue(const std::shared_ptr<Clock>& clock,
+ const std::shared_ptr<Engine>& engine,
+ const std::shared_ptr<Timezone>& tz)
+ {
+ // create an upcoming-events planner that =always= tracks the clock's date
+ auto range_planner = std::make_shared<SimpleRangePlanner>(engine, tz);
+ auto upcoming_planner = std::make_shared<UpcomingPlanner>(range_planner, clock->localtime());
+ clock->date_changed.connect([clock,upcoming_planner](){
+ const auto now = clock->localtime();
+ g_debug("refretching appointments due to date change: %s", now.format("%F %T").c_str());
+ upcoming_planner->date().set(now);
+ });
+
+ auto wakeup_timer = create_wakeup_timer(clock);
+ return std::make_shared<SimpleAlarmQueue>(clock, upcoming_planner, wakeup_timer);
+ }
+}
+
int
main(int /*argc*/, char** /*argv*/)
{
@@ -54,35 +129,16 @@ main(int /*argc*/, char** /*argv*/)
bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR);
textdomain(GETTEXT_PACKAGE);
- // we don't show appointments in the greeter,
- // so no need to connect to EDS there...
- std::shared_ptr<Engine> engine;
- if (!g_strcmp0("lightdm", g_get_user_name()))
- engine.reset(new MockEngine);
- else
- engine.reset(new EdsEngine);
-
- // build the state, actions, and menufactory
- std::shared_ptr<State> state(new State);
- std::shared_ptr<Settings> live_settings(new LiveSettings);
- std::shared_ptr<Timezones> live_timezones(new LiveTimezones(live_settings, TIMEZONE_FILE));
- std::shared_ptr<Clock> live_clock(new LiveClock(live_timezones));
- std::shared_ptr<Timezone> file_timezone(new FileTimezone(TIMEZONE_FILE));
- const auto now = live_clock->localtime();
- state->settings = live_settings;
- state->clock = live_clock;
- state->locations.reset(new SettingsLocations(live_settings, live_timezones));
- auto calendar_month = new MonthPlanner(std::shared_ptr<RangePlanner>(new SimpleRangePlanner(engine, file_timezone)), now);
- state->calendar_month.reset(calendar_month);
- state->calendar_upcoming.reset(new UpcomingPlanner(std::shared_ptr<RangePlanner>(new SimpleRangePlanner(engine, file_timezone)), now));
- std::shared_ptr<Actions> actions(new LiveActions(state));
+ auto engine = create_engine();
+ auto timezone = std::make_shared<FileTimezone>(TIMEZONE_FILE);
+ auto state = create_state(engine, timezone);
+ auto actions = std::make_shared<LiveActions>(state);
MenuFactory factory(actions, state);
- // snap decisions
- std::shared_ptr<UpcomingPlanner> upcoming_planner(new UpcomingPlanner(std::shared_ptr<RangePlanner>(new SimpleRangePlanner(engine, file_timezone)), now));
- ClockWatcherImpl clock_watcher(live_clock, upcoming_planner);
+ // set up the snap decisions
Snap snap;
- clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){
+ auto alarm_queue = create_simple_alarm_queue(state->clock, engine, timezone);
+ alarm_queue->alarm_reached().connect([&snap](const Appointment& appt){
auto snap_show = [](const Appointment& a){
const char* url;
if(!a.url.empty())
diff --git a/src/planner-range.cpp b/src/planner-range.cpp
index 93946e0..41b0f56 100644
--- a/src/planner-range.cpp
+++ b/src/planner-range.cpp
@@ -34,7 +34,7 @@ SimpleRangePlanner::SimpleRangePlanner(const std::shared_ptr<Engine>& engine,
m_range(std::pair<DateTime,DateTime>(DateTime::NowLocal(), DateTime::NowLocal()))
{
engine->changed().connect([this](){
- g_debug("RangePlanner %p rebuilding soon because Engine %p emitted 'changed' signal%p", this, m_engine.get());
+ g_debug("RangePlanner %p rebuilding soon because Engine %p emitted 'changed' signal", this, m_engine.get());
rebuild_soon();
});
diff --git a/src/wakeup-timer-mainloop.cpp b/src/wakeup-timer-mainloop.cpp
new file mode 100644
index 0000000..dfef037
--- /dev/null
+++ b/src/wakeup-timer-mainloop.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/wakeup-timer-mainloop.h>
+
+#include <glib.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+class MainloopWakeupTimer::Impl
+{
+
+public:
+
+ Impl(const std::shared_ptr<Clock>& clock):
+ m_clock(clock)
+ {
+ }
+
+ ~Impl()
+ {
+ cancel_timer();
+ }
+
+ void set_wakeup_time(const DateTime& d)
+ {
+ m_wakeup_time = d;
+
+ rebuild_timer();
+ }
+
+ core::Signal<>& timeout() { return m_timeout; }
+
+private:
+
+ void rebuild_timer()
+ {
+ cancel_timer();
+
+ g_return_if_fail(m_wakeup_time.is_set());
+
+ const auto now = m_clock->localtime();
+ const auto difference_usec = g_date_time_difference(m_wakeup_time.get(), now.get());
+ const guint interval_msec = (guint)difference_usec / 1000u;
+ g_debug("%s setting wakeup timer to kick at %s, which is in %zu seconds",
+ G_STRFUNC,
+ m_wakeup_time.format("%F %T").c_str(),
+ size_t{interval_msec/1000});
+
+ m_timeout_tag = g_timeout_add_full(G_PRIORITY_HIGH,
+ interval_msec,
+ on_timeout,
+ this,
+ nullptr);
+ }
+
+ static gboolean on_timeout(gpointer gself)
+ {
+ g_debug("%s %s", G_STRLOC, G_STRFUNC);
+ static_cast<Impl*>(gself)->on_timeout();
+ return G_SOURCE_REMOVE;
+ }
+
+ void on_timeout()
+ {
+ cancel_timer();
+ m_timeout();
+ }
+
+ void cancel_timer()
+ {
+ if (m_timeout_tag != 0)
+ {
+ g_source_remove(m_timeout_tag);
+ m_timeout_tag = 0;
+ }
+ }
+
+ core::Signal<> m_timeout;
+ const std::shared_ptr<Clock>& m_clock;
+ guint m_timeout_tag = 0;
+ DateTime m_wakeup_time;
+};
+
+/***
+****
+***/
+
+MainloopWakeupTimer::MainloopWakeupTimer(const std::shared_ptr<Clock>& clock):
+ p(new Impl(clock))
+{
+}
+
+MainloopWakeupTimer::~MainloopWakeupTimer()
+{
+}
+
+void MainloopWakeupTimer::set_wakeup_time(const DateTime& d)
+{
+ p->set_wakeup_time(d);
+}
+
+core::Signal<>& MainloopWakeupTimer::timeout()
+{
+ return p->timeout();
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/src/wakeup-timer-uha.cpp b/src/wakeup-timer-uha.cpp
new file mode 100644
index 0000000..b634f52
--- /dev/null
+++ b/src/wakeup-timer-uha.cpp
@@ -0,0 +1,167 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/wakeup-timer-uha.h>
+
+#include <ubuntu/hardware/alarm.h>
+
+#include <glib.h>
+
+#include <unistd.h>
+
+#include <ctime> // struct timespec
+#include <mutex>
+#include <thread>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+class UhaWakeupTimer::Impl
+{
+
+public:
+
+ Impl(const std::shared_ptr<Clock>& clock):
+ m_clock(clock),
+ m_hardware_alarm(u_hardware_alarm_create())
+ {
+ // fire up a worker thread that initially just sleeps
+ set_wakeup_time_to_the_distant_future();
+ m_thread = std::move(std::thread([&](){threadfunc();}));
+ }
+
+ ~Impl()
+ {
+ // tell the worker thread to wake up and exit
+ m_yielding = true;
+ set_wakeup_time(m_clock->localtime().add_full(0,0,0,0,0,0.1));
+
+ // wait for it to happen
+ if (m_thread.joinable())
+ m_thread.join();
+
+ g_idle_remove_by_data(this);
+
+ u_hardware_alarm_unref(m_hardware_alarm);
+ }
+
+ void set_wakeup_time(const DateTime& d)
+ {
+ g_debug("%s %s", G_STRLOC, G_STRFUNC);
+ std::lock_guard<std::recursive_mutex> lg(m_mutex);
+
+ const auto diff_usec = d - m_clock->localtime();
+ struct timespec ts;
+ ts.tv_sec = diff_usec / G_USEC_PER_SEC;
+ ts.tv_nsec = (diff_usec % G_USEC_PER_SEC) * 1000;
+ g_debug("%s setting hardware wakeup time to %s (%zu seconds from now)",
+ G_STRFUNC, (size_t)ts.tv_sec, d.format("%F %T").c_str());
+ u_hardware_alarm_set_relative_to_with_behavior(m_hardware_alarm,
+ U_HARDWARE_ALARM_TIME_REFERENCE_NOW,
+ U_HARDWARE_ALARM_SLEEP_BEHAVIOR_WAKEUP_DEVICE,
+ &ts);
+ }
+
+ core::Signal<>& timeout() { return m_timeout; }
+
+private:
+
+ void set_wakeup_time_to_the_distant_future()
+ {
+ const auto next_year = m_clock->localtime().add_full(1,0,0,0,0,0);
+ set_wakeup_time(next_year);
+ }
+
+ static gboolean kick_idle (gpointer gself)
+ {
+ static_cast<Impl*>(gself)->timeout();
+
+ return G_SOURCE_REMOVE;
+ }
+
+ void threadfunc()
+ {
+ while (!m_yielding)
+ {
+ // wait for the next hw alarm
+ UHardwareAlarmWaitResult wait_result;
+ auto rc = u_hardware_alarm_wait_for_next_alarm(m_hardware_alarm, &wait_result);
+ g_return_if_fail (rc == U_STATUS_SUCCESS);
+
+ // set a long wakeup interval for the next iteration of the loop.
+ // if there's another Appointment queued up by the Planner,
+ // our timeout() listener will call set_wakeup_time() to set the
+ // real wakeup interval.
+ set_wakeup_time_to_the_distant_future();
+
+ // delegate the kick back to the main thread
+ g_idle_add (kick_idle, this);
+ }
+ }
+
+ core::Signal<> m_timeout;
+ std::recursive_mutex m_mutex;
+ bool m_yielding = false;
+ const std::shared_ptr<Clock>& m_clock;
+ UHardwareAlarm m_hardware_alarm = nullptr;
+ std::thread m_thread;
+};
+
+/***
+****
+***/
+
+UhaWakeupTimer::UhaWakeupTimer(const std::shared_ptr<Clock>& clock):
+ p(new Impl(clock))
+{
+}
+
+UhaWakeupTimer::~UhaWakeupTimer()
+{
+}
+
+bool UhaWakeupTimer::is_supported()
+{
+ auto hw_alarm = u_hardware_alarm_create();
+ g_debug ("%s hardware alarm %p", G_STRFUNC, hw_alarm);
+ return hw_alarm != nullptr;
+}
+
+void UhaWakeupTimer::set_wakeup_time(const DateTime& d)
+{
+ p->set_wakeup_time(d);
+}
+
+core::Signal<>& UhaWakeupTimer::timeout()
+{
+ return p->timeout();
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 7d590c9..fe6d7eb 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -41,8 +41,8 @@ function(add_test_by_name name)
target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
endfunction()
add_test_by_name(test-actions)
+add_test_by_name(test-alarm-queue)
add_test_by_name(test-clock)
-add_test_by_name(test-clock-watcher)
add_test_by_name(test-exporter)
add_test_by_name(test-formatter)
add_test_by_name(test-live-actions)
diff --git a/tests/test-clock-watcher.cpp b/tests/test-alarm-queue.cpp
index 2425fe8..d2de8ac 100644
--- a/tests/test-clock-watcher.cpp
+++ b/tests/test-alarm-queue.cpp
@@ -17,7 +17,8 @@
* Charles Kerr <charles.kerr@canonical.com>
*/
-#include <datetime/clock-watcher.h>
+#include <datetime/alarm-queue-simple.h>
+#include <datetime/wakeup-timer-mainloop.h>
#include <gtest/gtest.h>
@@ -25,7 +26,7 @@
using namespace unity::indicator::datetime;
-class ClockWatcherFixture: public StateFixture
+class AlarmQueueFixture: public StateFixture
{
private:
@@ -34,7 +35,8 @@ private:
protected:
std::vector<std::string> m_triggered;
- std::unique_ptr<ClockWatcher> m_watcher;
+ std::shared_ptr<WakeupTimer> m_wakeup_timer;
+ std::unique_ptr<AlarmQueue> m_watcher;
std::shared_ptr<RangePlanner> m_range_planner;
std::shared_ptr<UpcomingPlanner> m_upcoming;
@@ -42,9 +44,10 @@ protected:
{
super::SetUp();
+ m_wakeup_timer.reset(new MainloopWakeupTimer(m_state->clock));
m_range_planner.reset(new MockRangePlanner);
m_upcoming.reset(new UpcomingPlanner(m_range_planner, m_state->clock->localtime()));
- m_watcher.reset(new ClockWatcherImpl(m_state->clock, m_upcoming));
+ m_watcher.reset(new SimpleAlarmQueue(m_state->clock, m_upcoming, m_wakeup_timer));
m_watcher->alarm_reached().connect([this](const Appointment& appt){
m_triggered.push_back(appt.uid);
});
@@ -108,7 +111,7 @@ protected:
****
***/
-TEST_F(ClockWatcherFixture, AppointmentsChanged)
+TEST_F(AlarmQueueFixture, AppointmentsChanged)
{
// Add some appointments to the planner.
// One of these matches our state's localtime, so that should get triggered.
@@ -117,12 +120,12 @@ TEST_F(ClockWatcherFixture, AppointmentsChanged)
m_range_planner->appointments().set(a);
// Confirm that it got fired
- EXPECT_EQ(1, m_triggered.size());
+ ASSERT_EQ(1, m_triggered.size());
EXPECT_EQ(a[0].uid, m_triggered[0]);
}
-TEST_F(ClockWatcherFixture, TimeChanged)
+TEST_F(AlarmQueueFixture, TimeChanged)
{
// Add some appointments to the planner.
// Neither of these match the state's localtime, so nothing should be triggered.
@@ -132,26 +135,27 @@ TEST_F(ClockWatcherFixture, TimeChanged)
// Set the state's clock to a time that matches one of the appointments().
// That appointment should get triggered.
+g_message ("%s setting clock to %s", G_STRLOC, a[1].begin.format("%F %T").c_str());
m_mock_state->mock_clock->set_localtime(a[1].begin);
- EXPECT_EQ(1, m_triggered.size());
+ ASSERT_EQ(1, m_triggered.size());
EXPECT_EQ(a[1].uid, m_triggered[0]);
}
-TEST_F(ClockWatcherFixture, MoreThanOne)
+TEST_F(AlarmQueueFixture, MoreThanOne)
{
const auto now = m_state->clock->localtime();
std::vector<Appointment> a = build_some_appointments();
a[0].begin = a[1].begin = now;
m_range_planner->appointments().set(a);
- EXPECT_EQ(2, m_triggered.size());
+ ASSERT_EQ(2, m_triggered.size());
EXPECT_EQ(a[0].uid, m_triggered[0]);
EXPECT_EQ(a[1].uid, m_triggered[1]);
}
-TEST_F(ClockWatcherFixture, NoDuplicates)
+TEST_F(AlarmQueueFixture, NoDuplicates)
{
// Setup: add an appointment that gets triggered.
const auto now = m_state->clock->localtime();
@@ -160,13 +164,13 @@ TEST_F(ClockWatcherFixture, NoDuplicates)
a.push_back(appointments[0]);
a[0].begin = now;
m_range_planner->appointments().set(a);
- EXPECT_EQ(1, m_triggered.size());
+ ASSERT_EQ(1, m_triggered.size());
EXPECT_EQ(a[0].uid, m_triggered[0]);
// Now change the appointment vector by adding one to it.
- // Confirm that the ClockWatcher doesn't re-trigger a[0]
+ // Confirm that the AlarmQueue doesn't re-trigger a[0]
a.push_back(appointments[1]);
m_range_planner->appointments().set(a);
- EXPECT_EQ(1, m_triggered.size());
+ ASSERT_EQ(1, m_triggered.size());
EXPECT_EQ(a[0].uid, m_triggered[0]);
}