diff options
-rw-r--r-- | CMakeLists.txt | 7 | ||||
-rw-r--r-- | MERGE-REVIEW | 35 | ||||
-rw-r--r-- | debian/control | 2 | ||||
-rw-r--r-- | include/datetime/alarm-queue-simple.h | 63 | ||||
-rw-r--r-- | include/datetime/alarm-queue.h (renamed from include/datetime/clock-watcher.h) | 40 | ||||
-rw-r--r-- | include/datetime/date-time.h | 3 | ||||
-rw-r--r-- | include/datetime/planner-range.h | 2 | ||||
-rw-r--r-- | include/datetime/wakeup-timer-mainloop.h | 62 | ||||
-rw-r--r-- | include/datetime/wakeup-timer-uha.h | 64 | ||||
-rw-r--r-- | include/datetime/wakeup-timer.h | 55 | ||||
-rw-r--r-- | src/CMakeLists.txt | 55 | ||||
-rw-r--r-- | src/alarm-queue-simple.cpp | 159 | ||||
-rw-r--r-- | src/clock-watcher.cpp | 81 | ||||
-rw-r--r-- | src/date-time.cpp | 5 | ||||
-rw-r--r-- | src/engine-eds.cpp | 12 | ||||
-rw-r--r-- | src/main.cpp | 115 | ||||
-rw-r--r-- | src/planner-range.cpp | 2 | ||||
-rw-r--r-- | src/wakeup-timer-mainloop.cpp | 138 | ||||
-rw-r--r-- | src/wakeup-timer-uha.cpp | 175 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/test-alarm-queue.cpp (renamed from tests/test-clock-watcher.cpp) | 32 |
21 files changed, 925 insertions, 184 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 378dc03..1390f44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ set (CMAKE_INSTALL_FULL_PKGLIBEXECDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${CMAKE_ ## find_package (PkgConfig REQUIRED) +include (CheckIncludeFile) include (FindPkgConfig) pkg_check_modules (SERVICE_DEPS REQUIRED @@ -43,6 +44,12 @@ pkg_check_modules (SERVICE_DEPS REQUIRED properties-cpp>=0.0.1) include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS}) +CHECK_INCLUDE_FILE(ubuntu/hardware/alarm.h HAVE_UBUNTU_HW_ALARM_H) +if (HAVE_UBUNTU_HW_ALARM_H) + set (SERVICE_DEPS_LIBRARIES -lubuntu_platform_hardware_api ${SERVICE_DEPS_LIBRARIES}) + add_definitions(-DHAVE_UBUNTU_HW_ALARM_H) +endif () + ## ## custom targets ## diff --git a/MERGE-REVIEW b/MERGE-REVIEW index 5e40f45..1a5815c 100644 --- a/MERGE-REVIEW +++ b/MERGE-REVIEW @@ -6,14 +6,39 @@ and reviewers should ensure that they've done for a merge into the project. * Ensure the project compiles and the test suite executes without error * Ensure that non-obvious code has comments explaining it - * If the change works on specific profiles, please include those in the merge description. + * If the change affects specific features, please reference the appropriate + tags in the merge description so reviewers can test appropriately: + [phone profile], [desktop profile], [alarms] == Reviewer Responsibilities == * Did the Jenkins build compile? Pass? Run unit tests successfully? * Are there appropriate tests to cover any new functionality? - * If the description says this effects the phone profile: - * Run tests indicator-datetime/unity8* - * If the description says this effects the desktop profile: - * Run tests indicator-datetime/unity7* + * Do the tag-specific tests pass? + +== Phone Profile Tests == + + * Run tests indicator-datetime/unity8* + +== Desktop Profile Tests == + + * Run tests indicator-datetime/unity7* + +== Alarm Tests == + + * Hardware wakeups for new alarms: + 1. Create and save an upcoming alarm in ubuntu-clock-app. + 2. Unplug the phone from any USB connection and put it to sleep. + 3. Confirm that the alarm sounds on time even if the phone is asleep. + (Note: if in doubt about sleep you can see in the syslog whether the + device actually suspended or whether the suspend was aborted) + + * Hardware wakeups for edited alarms: + 1. Edit an alarm that's already passed. (see previous test) + 2. Unplug the phone from any USB connection and put it to sleep. + 3. Confirm that the alarm sounds on time even if the phone is asleep. + (Note: if in doubt about sleep you can see in the syslog whether the + device actually suspended or whether the suspend was aborted.) + + diff --git a/debian/control b/debian/control index 1e7b24e..9275e3e 100644 --- a/debian/control +++ b/debian/control @@ -19,6 +19,8 @@ Build-Depends: cmake, libedataserver1.2-dev (>= 3.5), liburl-dispatcher1-dev, libproperties-cpp-dev, + libplatform-hardware-api-headers [armhf i386 amd64], + libplatform-hardware-api1-dev [armhf i386 amd64], libdbustest1-dev, locales, Standards-Version: 3.9.3 diff --git a/include/datetime/alarm-queue-simple.h b/include/datetime/alarm-queue-simple.h new file mode 100644 index 0000000..d191aec --- /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::pair<std::string,DateTime>> 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_datetime; +}; + + +} // 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..4be35f7 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 != 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..ffa1523 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,30 +7,37 @@ SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${CXX_WARNING_ARGS} ${GCO add_definitions (-DTIMEZONE_FILE="/etc/timezone" -DG_LOG_DOMAIN="Indicator-Datetime") -add_library (${SERVICE_LIB} STATIC - actions.cpp - actions-live.cpp - appointment.cpp - clock.cpp - clock-live.cpp - clock-watcher.cpp - date-time.cpp - engine-eds.cpp - exporter.cpp - formatter.cpp - formatter-desktop.cpp - locations.cpp - locations-settings.cpp - menu.cpp - planner-month.cpp - planner-range.cpp - planner-upcoming.cpp - settings-live.cpp - snap.cpp - timezone-file.cpp - timezone-geoclue.cpp - timezones-live.cpp - utils.c) +set (SERVICE_SOURCES + actions.cpp + actions-live.cpp + alarm-queue-simple.cpp + appointment.cpp + clock.cpp + clock-live.cpp + date-time.cpp + engine-eds.cpp + exporter.cpp + formatter.cpp + formatter-desktop.cpp + locations.cpp + locations-settings.cpp + menu.cpp + planner-month.cpp + planner-range.cpp + planner-upcoming.cpp + settings-live.cpp + snap.cpp + timezone-file.cpp + timezone-geoclue.cpp + timezones-live.cpp + utils.c + wakeup-timer-mainloop.cpp) + +if (HAVE_UBUNTU_HW_ALARM_H) + set (SERVICE_SOURCES ${SERVICE_SOURCES} wakeup-timer-uha.cpp) +endif () + +add_library (${SERVICE_LIB} STATIC ${SERVICE_SOURCES}) 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..fa6c0bc --- /dev/null +++ b/src/alarm-queue-simple.cpp @@ -0,0 +1,159 @@ +/* + * 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_datetime(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_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(); +} + +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()) + { + const std::pair<std::string,DateTime> trig {current.uid, current.begin}; + m_triggered.insert(trig); + 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()); + + const auto appointments = m_planner->appointments().get(); + g_debug ("planner has %zu appointments in it", (size_t)appointments.size()); + + for(const auto& walk : appointments) + { + const std::pair<std::string,DateTime> trig {walk.uid, walk.begin}; + if (m_triggered.count(trig)) + 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()) + { + const std::pair<std::string,DateTime> trig {walk.uid, walk.begin}; + if (m_triggered.count(trig)) // 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/engine-eds.cpp b/src/engine-eds.cpp index da93206..1949193 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -418,6 +418,14 @@ private: auto status = ICAL_STATUS_NONE; e_cal_component_get_status(component, &status); + const auto begin_dt = DateTime(begin); + const auto end_dt = DateTime(end); + g_debug ("got appointment from %s to %s, uid %s status %d", + begin_dt.format("%F %T").c_str(), + end_dt.format("%F %T").c_str(), + uid, + (int)status); + if ((uid != nullptr) && (status != ICAL_STATUS_COMPLETED) && (status != ICAL_STATUS_CANCELLED)) @@ -430,8 +438,8 @@ private: if (text.value) appointment.summary = text.value; - appointment.begin = DateTime(begin); - appointment.end = DateTime(end); + appointment.begin = begin_dt; + appointment.end = end_dt; appointment.color = subtask->color; appointment.uid = uid; diff --git a/src/main.cpp b/src/main.cpp index c7b35e5..079fe35 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,11 @@ #include <datetime/state.h> #include <datetime/timezone-file.h> #include <datetime/timezones-live.h> +#include <datetime/wakeup-timer-mainloop.h> + +#ifdef HAVE_UBUNTU_HW_ALARM_H + #include <datetime/wakeup-timer-uha.h> +#endif #include <glib/gi18n.h> // bindtextdomain() #include <gio/gio.h> @@ -42,6 +47,81 @@ 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; + +#ifdef HAVE_UBUNTU_HW_ALARM_H + if (UhaWakeupTimer::is_supported()) // prefer to use the platform API + wakeup_timer = std::make_shared<UhaWakeupTimer>(clock); + else +#endif + 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 +134,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..4f99c8c --- /dev/null +++ b/src/wakeup-timer-mainloop.cpp @@ -0,0 +1,138 @@ +/* + * 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> + +#include <cstdlib> // abs() + +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 = std::abs(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..437eda2 --- /dev/null +++ b/src/wakeup-timer-uha.cpp @@ -0,0 +1,175 @@ +/* + * 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 wakeup_time = d.to_unix(); + + // simple sanity check: don't try to wait for something that's already passed + const auto now = m_clock->localtime().to_unix(); + g_return_if_fail (wakeup_time >= now); + + struct timespec sleep_interval; + sleep_interval.tv_sec = wakeup_time; + sleep_interval.tv_nsec = 0; + g_debug("%s %s setting hardware wakeup time to %s (%zu seconds from now)", + G_STRLOC, G_STRFUNC, + d.format("%F %T").c_str(), + (size_t)(wakeup_time - now)); + u_hardware_alarm_set_relative_to_with_behavior(m_hardware_alarm, + U_HARDWARE_ALARM_TIME_REFERENCE_RTC, + U_HARDWARE_ALARM_SLEEP_BEHAVIOR_WAKEUP_DEVICE, + &sleep_interval); + } + + core::Signal<>& timeout() { return m_timeout; } + +private: + + void set_wakeup_time_to_the_distant_future() + { + const auto tomorrow = m_clock->localtime().add_full(0,0,1,0,0,0); + set_wakeup_time(tomorrow); + } + + static gboolean kick_idle (gpointer gself) + { + static_cast<Impl*>(gself)->m_timeout(); + + return G_SOURCE_REMOVE; + } + + void threadfunc() + { + while (!m_yielding) + { + // wait for the next hw alarm + UHardwareAlarmWaitResult wait_result; + g_debug ("calling wait_for_next_alarm"); + 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]); } |