From 432db089ea22325c85718d43e4fbe148cbb94109 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 25 Mar 2015 10:43:06 -0500 Subject: change the default alarm duration from 30 minutes to 10. --- data/com.canonical.indicator.datetime.gschema.xml.in | 2 +- tests/test-exporter.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/com.canonical.indicator.datetime.gschema.xml.in b/data/com.canonical.indicator.datetime.gschema.xml.in index edcede2..044f694 100644 --- a/data/com.canonical.indicator.datetime.gschema.xml.in +++ b/data/com.canonical.indicator.datetime.gschema.xml.in @@ -148,7 +148,7 @@ - 30 + 10 <_summary>The alarm's duration. <_description> How long the alarm's sound will be looped if its snap decision is not dismissed by the user. diff --git a/tests/test-exporter.cpp b/tests/test-exporter.cpp index be8fcc3..d5118ac 100644 --- a/tests/test-exporter.cpp +++ b/tests/test-exporter.cpp @@ -222,7 +222,7 @@ TEST_F(ExporterFixture, AlarmProperties) g_clear_pointer (&haptic, g_free); /*** - **** Try chaning the DBus properties -- do the Settings change to match it? + **** Try changing the DBus properties -- do the Settings change to match it? ***/ expected_volume = 100; -- cgit v1.2.3 From 9e29a4fdb99b722c43af1268312db68fff17fc60 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 25 Mar 2015 12:42:28 -0500 Subject: 60 seconds after triggering an alarm, release our keepDisplayOn request --- include/notifications/awake.h | 6 ++ src/awake.cpp | 134 +++++++++++++++++++++++++++--------------- src/snap.cpp | 23 +++++++- 3 files changed, 113 insertions(+), 50 deletions(-) diff --git a/include/notifications/awake.h b/include/notifications/awake.h index bc38817..9c9d434 100644 --- a/include/notifications/awake.h +++ b/include/notifications/awake.h @@ -39,6 +39,12 @@ public: explicit Awake(const std::string& app_name); ~Awake(); + /** Whether or not the display is forced on */ + bool display_forced() const; + + /** Set the display force on/off */ + void set_display_forced(bool forced); + private: class Impl; std::unique_ptr impl; diff --git a/src/awake.cpp b/src/awake.cpp index 57358ab..fda4099 100644 --- a/src/awake.cpp +++ b/src/awake.cpp @@ -22,7 +22,7 @@ #include -#include +#include namespace unity { namespace indicator { @@ -56,6 +56,19 @@ public: } } + bool display_forced() const + { + return !m_screen_cookies.empty(); + } + + void set_display_forced(bool force) + { + if (force) + force_screen(); + else if (!force) + unforce_screen(); + } + private: static void on_system_bus_ready (GObject *, @@ -95,19 +108,7 @@ private: on_force_awake_response, self); - // ask unity-system-compositor to turn on the screen - g_dbus_connection_call (system_bus, - BUS_SCREEN_NAME, - BUS_SCREEN_PATH, - BUS_SCREEN_INTERFACE, - "keepDisplayOn", - nullptr, - G_VARIANT_TYPE("(i)"), - G_DBUS_CALL_FLAGS_NONE, - -1, - self->m_cancellable, - on_force_screen_response, - self); + self->force_screen(); g_object_unref (system_bus); } @@ -146,6 +147,53 @@ private: } } + void unforce_awake () + { + g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); + + if (m_awake_cookie != nullptr) + { + g_dbus_connection_call (m_system_bus, + BUS_POWERD_NAME, + BUS_POWERD_PATH, + BUS_POWERD_INTERFACE, + "clearSysState", + g_variant_new("(s)", m_awake_cookie), + nullptr, + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + nullptr, + nullptr); + + g_clear_pointer (&m_awake_cookie, g_free); + } + } + + /*** + **** FORCE DISPLAY ON + ***/ + + void force_screen() + { + g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); + g_return_if_fail (m_screen_cookies.empty()); + + // ask unity-system-compositor to turn on the screen + g_dbus_connection_call (m_system_bus, + BUS_SCREEN_NAME, + BUS_SCREEN_PATH, + BUS_SCREEN_INTERFACE, + "keepDisplayOn", + nullptr, + G_VARIANT_TYPE("(i)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + m_cancellable, + on_force_screen_response, + this); + } + static void on_force_screen_response (GObject * connection, GAsyncResult * res, gpointer gself) @@ -171,34 +219,11 @@ private: { auto self = static_cast(gself); - self->m_screen_cookie = NO_SCREEN_COOKIE; - g_variant_get (args, "(i)", &self->m_screen_cookie); - g_debug ("m_screen_cookie is now '%d'", self->m_screen_cookie); - + gint32 cookie = 0; + g_variant_get (args, "(i)", &cookie); g_variant_unref (args); - } - } - - void unforce_awake () - { - g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); - - if (m_awake_cookie != nullptr) - { - g_dbus_connection_call (m_system_bus, - BUS_POWERD_NAME, - BUS_POWERD_PATH, - BUS_POWERD_INTERFACE, - "clearSysState", - g_variant_new("(s)", m_awake_cookie), - nullptr, - G_DBUS_CALL_FLAGS_NONE, - -1, - nullptr, - nullptr, - nullptr); - - g_clear_pointer (&m_awake_cookie, g_free); + g_debug ("got screen_cookie '%d'", (int)cookie); + self->m_screen_cookies.insert (cookie); } } @@ -206,32 +231,34 @@ private: { g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); - if (m_screen_cookie != NO_SCREEN_COOKIE) + for (auto cookie : m_screen_cookies) { g_dbus_connection_call (m_system_bus, BUS_SCREEN_NAME, BUS_SCREEN_PATH, BUS_SCREEN_INTERFACE, "removeDisplayOnRequest", - g_variant_new("(i)", m_screen_cookie), + g_variant_new("(i)", cookie), nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr, nullptr); - - m_screen_cookie = NO_SCREEN_COOKIE; } + + m_screen_cookies.clear(); } const std::string m_app_name; GCancellable * m_cancellable = nullptr; GDBusConnection * m_system_bus = nullptr; char * m_awake_cookie = nullptr; - int32_t m_screen_cookie = NO_SCREEN_COOKIE; - static constexpr int32_t NO_SCREEN_COOKIE { std::numeric_limits::min() }; + // In general there will only be one cookie in this container... + // holding N cookies is a safeguard in case the client calls force_screen() + // while there's aleady a pending keepDisplayOn call on the bus. + std::set m_screen_cookies; }; /*** @@ -247,6 +274,19 @@ Awake::~Awake() { } +bool +Awake::display_forced() const +{ + return impl->display_forced(); +} + +void +Awake::set_display_forced(bool forced) +{ + impl->set_display_forced(forced); +} + + /*** **** ***/ diff --git a/src/snap.cpp b/src/snap.cpp index e655d2d..80f200d 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -88,8 +88,9 @@ public: intervention and shouldn't loop the sound. */ const bool interactive = appointment.is_ubuntu_alarm() && m_engine->supports_actions(); - // force the system to stay awake - auto awake = std::make_shared(m_engine->app_name()); + // Force the system to stay awake. + // In a clean world this would be a shared_ptr, but we use it as a GSourceFunc arg... + auto awake = new uin::Awake(m_engine->app_name()); // calendar events are muted in silent mode; alarm clocks never are std::shared_ptr sound; @@ -137,15 +138,31 @@ public: b.add_action ("snooze", _("Snooze")); } + // Don't keep the screen on forever. + // If the alarm keeps going on and on, release our screen-on lock + // after awhile to prevent unneccesary battery drain + constexpr int screen_awake_seconds { 60 }; + auto awake_timeout_func = [](gpointer a){ + static_cast(a)->set_display_forced(false); + return G_SOURCE_REMOVE; + }; + auto awake_timeout_tag = g_timeout_add_seconds (screen_awake_seconds, + awake_timeout_func, + awake); + // add 'sound', 'haptic', and 'awake' objects to the capture so // they stay alive until the closed callback is called; i.e., // for the lifespan of the notficiation - b.set_closed_callback([appointment, snooze, ok, sound, awake, haptic] + b.set_closed_callback([appointment, snooze, ok, sound, haptic, + awake, awake_timeout_tag] (const std::string& action){ if (action == "snooze") snooze(appointment); else ok(appointment); + + g_source_remove(awake_timeout_tag); + delete awake; }); const auto key = m_engine->show(b); -- cgit v1.2.3 From 4bf70c7afae44d4005d25105690304b4e9e361f3 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 25 Mar 2015 15:47:22 -0500 Subject: the previous commit made the Awake API too complicated... simplifying. --- include/notifications/awake.h | 8 +- src/awake.cpp | 176 +++++++++++++++++++----------------------- src/snap.cpp | 26 ++----- 3 files changed, 87 insertions(+), 123 deletions(-) diff --git a/include/notifications/awake.h b/include/notifications/awake.h index 9c9d434..ad04cca 100644 --- a/include/notifications/awake.h +++ b/include/notifications/awake.h @@ -36,15 +36,9 @@ namespace notifications { class Awake { public: - explicit Awake(const std::string& app_name); + explicit Awake(const std::string& app_name, unsigned int display_on_seconds); ~Awake(); - /** Whether or not the display is forced on */ - bool display_forced() const; - - /** Set the display force on/off */ - void set_display_forced(bool forced); - private: class Impl; std::unique_ptr impl; diff --git a/src/awake.cpp b/src/awake.cpp index fda4099..a37d185 100644 --- a/src/awake.cpp +++ b/src/awake.cpp @@ -22,7 +22,7 @@ #include -#include +#include namespace unity { namespace indicator { @@ -36,9 +36,10 @@ class Awake::Impl { public: - Impl(const std::string& app_name): + Impl(const std::string& app_name, unsigned int display_on_seconds): m_app_name(app_name), - m_cancellable(g_cancellable_new()) + m_cancellable(g_cancellable_new()), + m_display_on_seconds(display_on_seconds) { g_bus_get(G_BUS_TYPE_SYSTEM, m_cancellable, on_system_bus_ready, this); } @@ -48,27 +49,20 @@ public: g_cancellable_cancel (m_cancellable); g_object_unref (m_cancellable); + if (m_display_on_timer) + { + g_source_remove (m_display_on_timer); + m_display_on_timer = 0; + } + if (m_system_bus != nullptr) { unforce_awake (); - unforce_screen (); + remove_display_on_request (); g_object_unref (m_system_bus); } } - bool display_forced() const - { - return !m_screen_cookies.empty(); - } - - void set_display_forced(bool force) - { - if (force) - force_screen(); - else if (!force) - unforce_screen(); - } - private: static void on_system_bus_ready (GObject *, @@ -108,7 +102,19 @@ private: on_force_awake_response, self); - self->force_screen(); + // ask unity-system-compositor to turn on the screen + g_dbus_connection_call (system_bus, + BUS_SCREEN_NAME, + BUS_SCREEN_PATH, + BUS_SCREEN_INTERFACE, + "keepDisplayOn", + nullptr, + G_VARIANT_TYPE("(i)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + self->m_cancellable, + on_keep_display_on_response, + self); g_object_unref (system_bus); } @@ -147,56 +153,9 @@ private: } } - void unforce_awake () - { - g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); - - if (m_awake_cookie != nullptr) - { - g_dbus_connection_call (m_system_bus, - BUS_POWERD_NAME, - BUS_POWERD_PATH, - BUS_POWERD_INTERFACE, - "clearSysState", - g_variant_new("(s)", m_awake_cookie), - nullptr, - G_DBUS_CALL_FLAGS_NONE, - -1, - nullptr, - nullptr, - nullptr); - - g_clear_pointer (&m_awake_cookie, g_free); - } - } - - /*** - **** FORCE DISPLAY ON - ***/ - - void force_screen() - { - g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); - g_return_if_fail (m_screen_cookies.empty()); - - // ask unity-system-compositor to turn on the screen - g_dbus_connection_call (m_system_bus, - BUS_SCREEN_NAME, - BUS_SCREEN_PATH, - BUS_SCREEN_INTERFACE, - "keepDisplayOn", - nullptr, - G_VARIANT_TYPE("(i)"), - G_DBUS_CALL_FLAGS_NONE, - -1, - m_cancellable, - on_force_screen_response, - this); - } - - static void on_force_screen_response (GObject * connection, - GAsyncResult * res, - gpointer gself) + static void on_keep_display_on_response (GObject * connection, + GAsyncResult * res, + gpointer gself) { GError * error; GVariant * args; @@ -219,54 +178,92 @@ private: { auto self = static_cast(gself); - gint32 cookie = 0; - g_variant_get (args, "(i)", &cookie); + self->m_display_on_cookie = NO_DISPLAY_ON_COOKIE; + g_variant_get (args, "(i)", &self->m_display_on_cookie); + g_debug ("m_display_on_cookie is now '%d'", self->m_display_on_cookie); + + self->m_display_on_timer = g_timeout_add_seconds (self->m_display_on_seconds, + on_display_on_timer, + gself); + g_variant_unref (args); - g_debug ("got screen_cookie '%d'", (int)cookie); - self->m_screen_cookies.insert (cookie); } } - void unforce_screen () + static gboolean on_display_on_timer (gpointer gself) + { + auto self = static_cast(gself); + + self->m_display_on_timer = 0; + self->remove_display_on_request(); + + return G_SOURCE_REMOVE; + } + + + void unforce_awake () { g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); - for (auto cookie : m_screen_cookies) + if (m_awake_cookie != nullptr) + { + g_dbus_connection_call (m_system_bus, + BUS_POWERD_NAME, + BUS_POWERD_PATH, + BUS_POWERD_INTERFACE, + "clearSysState", + g_variant_new("(s)", m_awake_cookie), + nullptr, + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + nullptr, + nullptr); + + g_clear_pointer (&m_awake_cookie, g_free); + } + } + + void remove_display_on_request () + { + g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); + + if (m_display_on_cookie != NO_DISPLAY_ON_COOKIE) { g_dbus_connection_call (m_system_bus, BUS_SCREEN_NAME, BUS_SCREEN_PATH, BUS_SCREEN_INTERFACE, "removeDisplayOnRequest", - g_variant_new("(i)", cookie), + g_variant_new("(i)", m_display_on_cookie), nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr, nullptr); - } - m_screen_cookies.clear(); + m_display_on_cookie = NO_DISPLAY_ON_COOKIE; + } } const std::string m_app_name; GCancellable * m_cancellable = nullptr; GDBusConnection * m_system_bus = nullptr; char * m_awake_cookie = nullptr; + int32_t m_display_on_cookie = NO_DISPLAY_ON_COOKIE; + const guint m_display_on_seconds; + guint m_display_on_timer; - // In general there will only be one cookie in this container... - // holding N cookies is a safeguard in case the client calls force_screen() - // while there's aleady a pending keepDisplayOn call on the bus. - std::set m_screen_cookies; + static constexpr int32_t NO_DISPLAY_ON_COOKIE { std::numeric_limits::min() }; }; /*** **** ***/ -Awake::Awake(const std::string& app_name): - impl(new Impl (app_name)) +Awake::Awake(const std::string& app_name, unsigned int display_on_seconds): + impl(new Impl (app_name, display_on_seconds)) { } @@ -274,19 +271,6 @@ Awake::~Awake() { } -bool -Awake::display_forced() const -{ - return impl->display_forced(); -} - -void -Awake::set_display_forced(bool forced) -{ - impl->set_display_forced(forced); -} - - /*** **** ***/ diff --git a/src/snap.cpp b/src/snap.cpp index 80f200d..4329eca 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -88,9 +88,11 @@ public: intervention and shouldn't loop the sound. */ const bool interactive = appointment.is_ubuntu_alarm() && m_engine->supports_actions(); - // Force the system to stay awake. - // In a clean world this would be a shared_ptr, but we use it as a GSourceFunc arg... - auto awake = new uin::Awake(m_engine->app_name()); + // keep the screen on for the first part of the alarm; + // keep the system awake for the duration of the alarm + constexpr unsigned int display_on_seconds = 60; + auto awake = std::make_shared(m_engine->app_name(), + display_on_seconds); // calendar events are muted in silent mode; alarm clocks never are std::shared_ptr sound; @@ -138,31 +140,15 @@ public: b.add_action ("snooze", _("Snooze")); } - // Don't keep the screen on forever. - // If the alarm keeps going on and on, release our screen-on lock - // after awhile to prevent unneccesary battery drain - constexpr int screen_awake_seconds { 60 }; - auto awake_timeout_func = [](gpointer a){ - static_cast(a)->set_display_forced(false); - return G_SOURCE_REMOVE; - }; - auto awake_timeout_tag = g_timeout_add_seconds (screen_awake_seconds, - awake_timeout_func, - awake); - // add 'sound', 'haptic', and 'awake' objects to the capture so // they stay alive until the closed callback is called; i.e., // for the lifespan of the notficiation - b.set_closed_callback([appointment, snooze, ok, sound, haptic, - awake, awake_timeout_tag] + b.set_closed_callback([appointment, snooze, ok, sound, awake, haptic] (const std::string& action){ if (action == "snooze") snooze(appointment); else ok(appointment); - - g_source_remove(awake_timeout_tag); - delete awake; }); const auto key = m_engine->show(b); -- cgit v1.2.3 From 845067b3f37ff48eab3b612d52518e529559b738 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 31 Mar 2015 13:54:26 -0500 Subject: simplify the awake display timeout code --- include/notifications/awake.h | 2 +- src/awake.cpp | 23 ++++++++++++++++------- src/snap.cpp | 7 ++----- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/include/notifications/awake.h b/include/notifications/awake.h index ad04cca..bc38817 100644 --- a/include/notifications/awake.h +++ b/include/notifications/awake.h @@ -36,7 +36,7 @@ namespace notifications { class Awake { public: - explicit Awake(const std::string& app_name, unsigned int display_on_seconds); + explicit Awake(const std::string& app_name); ~Awake(); private: diff --git a/src/awake.cpp b/src/awake.cpp index a37d185..e1bec6c 100644 --- a/src/awake.cpp +++ b/src/awake.cpp @@ -36,10 +36,9 @@ class Awake::Impl { public: - Impl(const std::string& app_name, unsigned int display_on_seconds): + Impl(const std::string& app_name): m_app_name(app_name), - m_cancellable(g_cancellable_new()), - m_display_on_seconds(display_on_seconds) + m_cancellable(g_cancellable_new()) { g_bus_get(G_BUS_TYPE_SYSTEM, m_cancellable, on_system_bus_ready, this); } @@ -251,9 +250,19 @@ private: GCancellable * m_cancellable = nullptr; GDBusConnection * m_system_bus = nullptr; char * m_awake_cookie = nullptr; + + /** + * As described by bug #1434637, alarms should have the display turn on, + * dim, and turn off "just like it would if you'd woken it up yourself". + * USC may be adding an intent-based bus API to handle this use case, + * e.g. turnDisplayOnTemporarily(intent), but there's no timeframe for it. + * + * Until that's avaialble, we can get close to Design's specs by + * requesting a display-on cookie and then releasing the cookie + * a moment later. */ + const guint m_display_on_seconds = 1; + guint m_display_on_timer = 0; int32_t m_display_on_cookie = NO_DISPLAY_ON_COOKIE; - const guint m_display_on_seconds; - guint m_display_on_timer; static constexpr int32_t NO_DISPLAY_ON_COOKIE { std::numeric_limits::min() }; }; @@ -262,8 +271,8 @@ private: **** ***/ -Awake::Awake(const std::string& app_name, unsigned int display_on_seconds): - impl(new Impl (app_name, display_on_seconds)) +Awake::Awake(const std::string& app_name): + impl(new Impl(app_name)) { } diff --git a/src/snap.cpp b/src/snap.cpp index 4329eca..e655d2d 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -88,11 +88,8 @@ public: intervention and shouldn't loop the sound. */ const bool interactive = appointment.is_ubuntu_alarm() && m_engine->supports_actions(); - // keep the screen on for the first part of the alarm; - // keep the system awake for the duration of the alarm - constexpr unsigned int display_on_seconds = 60; - auto awake = std::make_shared(m_engine->app_name(), - display_on_seconds); + // force the system to stay awake + auto awake = std::make_shared(m_engine->app_name()); // calendar events are muted in silent mode; alarm clocks never are std::shared_ptr sound; -- cgit v1.2.3 From 9f48c6479a17771597544adc36eac968b9b5c027 Mon Sep 17 00:00:00 2001 From: CI Train Bot Date: Tue, 31 Mar 2015 20:25:38 +0000 Subject: Releasing 13.10.0+15.04.20150331-0ubuntu1 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 40df883..62a5b4e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +indicator-datetime (13.10.0+15.04.20150331-0ubuntu1) vivid; urgency=medium + + [ Charles Kerr ] + * Reduce the forced screen-on time for alarms to reduce battery + consumption. Also, lower the default alarm duration from 30 minutes + to 10 minutes. (LP: #1434637) + + -- CI Train Bot Tue, 31 Mar 2015 20:25:38 +0000 + indicator-datetime (13.10.0+15.04.20150317-0ubuntu1) vivid; urgency=medium [ Charles Kerr ] -- cgit v1.2.3 From fff19d70649589b81a896e4deb032a7bd4bdca1e Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 31 Mar 2015 18:54:04 -0500 Subject: add an Alarm class to represent ical valarm components; change the Appointment class to hold an arbitrary number of Alarms. --- include/datetime/appointment.h | 26 ++++++- include/datetime/snap.h | 3 +- src/actions-live.cpp | 19 +++-- src/appointment.cpp | 13 +++- src/engine-eds.cpp | 163 ++++++++++++++++++++++++++++++++--------- src/main.cpp | 12 +-- src/snap.cpp | 16 ++-- tests/manual-test-snap.cpp | 10 +-- tests/test-live-actions.cpp | 6 -- tests/test-snap.cpp | 20 ++--- 10 files changed, 206 insertions(+), 82 deletions(-) diff --git a/include/datetime/appointment.h b/include/datetime/appointment.h index ab89c2f..337d9b8 100644 --- a/include/datetime/appointment.h +++ b/include/datetime/appointment.h @@ -21,14 +21,31 @@ #define INDICATOR_DATETIME_APPOINTMENT_H #include + +#include #include +#include +#include namespace unity { namespace indicator { namespace datetime { /** - * \brief Plain Old Data Structure that represents a calendar appointment. + * \brief Basic information required to raise a notification about some Appointment. + */ +struct Alarm +{ + std::string text; + std::string audio_url; + DateTime time; + std::chrono::seconds duration; + + bool operator== (const Alarm& that) const; +}; + +/** + * \brief An instance of an appointment; e.g. a calendar event or clock-app alarm * * @see Planner */ @@ -39,14 +56,15 @@ public: Type type = EVENT; bool is_ubuntu_alarm() const { return type == UBUNTU_ALARM; } + std::string uid; std::string color; std::string summary; - std::string url; - std::string uid; - std::string audio_url; + std::string activation_url; DateTime begin; DateTime end; + std::vector alarms; + bool operator== (const Appointment& that) const; }; diff --git a/include/datetime/snap.h b/include/datetime/snap.h index 572158d..cc091d3 100644 --- a/include/datetime/snap.h +++ b/include/datetime/snap.h @@ -42,8 +42,9 @@ public: const std::shared_ptr& settings); virtual ~Snap(); - typedef std::function appointment_func; + typedef std::function appointment_func; void operator()(const Appointment& appointment, + const Alarm& alarm, appointment_func snooze, appointment_func ok); diff --git a/src/actions-live.cpp b/src/actions-live.cpp index 4d1f770..3cbfb78 100644 --- a/src/actions-live.cpp +++ b/src/actions-live.cpp @@ -135,12 +135,19 @@ void LiveActions::phone_open_alarm_app() void LiveActions::phone_open_appointment(const Appointment& appt) { - if (!appt.url.empty()) - dispatch_url(appt.url); - else if (appt.is_ubuntu_alarm()) - phone_open_alarm_app(); - else - phone_open_calendar_app(DateTime::NowLocal()); + if (!appt.activation_url.empty()) + { + dispatch_url(appt.activation_url); + } + else switch (appt.type) + { + case Appointment::UBUNTU_ALARM: + phone_open_alarm_app(); + break; + + default: + phone_open_calendar_app(appt.begin); + } } void LiveActions::phone_open_calendar_app(const DateTime&) diff --git a/src/appointment.cpp b/src/appointment.cpp index ae71459..236c5f4 100644 --- a/src/appointment.cpp +++ b/src/appointment.cpp @@ -27,16 +27,23 @@ namespace datetime { ***** ****/ +bool Alarm::operator==(const Alarm& that) const +{ + return (text==that.text) + && (audio_url==that.audio_url) + && (this->time==that.time) + && (duration==that.duration); +} + bool Appointment::operator==(const Appointment& that) const { return (type==that.type) && (uid==that.uid) && (color==that.color) && (summary==that.summary) - && (url==that.url) - && (audio_url==that.audio_url) && (begin==that.begin) - && (end==that.end); + && (end==that.end) + && (alarms==that.alarms); } /**** diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 47c7a9b..820d9d4 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -126,12 +126,18 @@ public: auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR); const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension)); g_debug("calling e_cal_client_generate_instances for %p", (void*)client); + auto subtask = new AppointmentSubtask(main_task, + client, + color, + default_timezone, + begin_timet, + end_timet); e_cal_client_generate_instances(client, begin_timet, end_timet, m_cancellable, my_get_appointments_foreach, - new AppointmentSubtask (main_task, client, color), + subtask, [](gpointer g){delete static_cast(g);}); } } @@ -409,14 +415,71 @@ private: std::shared_ptr task; ECalClient* client; std::string color; - AppointmentSubtask(const std::shared_ptr& task_in, ECalClient* client_in, const char* color_in): - task(task_in), client(client_in) + icaltimezone* default_timezone; + time_t begin; + time_t end; + + AppointmentSubtask(const std::shared_ptr& task_in, + ECalClient* client_in, + const char* color_in, + icaltimezone* default_tz, + time_t begin_, + time_t end_): + task(task_in), + client(client_in), + default_timezone(default_tz), + begin(begin_), + end(end_) { if (color_in) color = color_in; } }; + static std::string get_alarm_text(ECalComponentAlarm * alarm) + { + std::string ret; + + ECalComponentAlarmAction action; + e_cal_component_alarm_get_action(alarm, &action); + if (action == E_CAL_COMPONENT_ALARM_DISPLAY) + { + ECalComponentText text; + text.value = nullptr; + e_cal_component_alarm_get_description(alarm, &text); + if (text.value) + ret = text.value; + } + + return ret; + } + + static std::string get_alarm_sound_url(ECalComponentAlarm * alarm) + { + std::string ret; + + ECalComponentAlarmAction action; + e_cal_component_alarm_get_action(alarm, &action); + if (action == E_CAL_COMPONENT_ALARM_AUDIO) + { + icalattach* attach = nullptr; + e_cal_component_alarm_get_attach(alarm, &attach); + if (attach != nullptr) + { + if (icalattach_get_is_url (attach)) + { + const char* url = icalattach_get_url(attach); + if (url != nullptr) + ret = url; + } + + icalattach_unref(attach); + } + } + + return ret; + } + static gboolean my_get_appointments_foreach(ECalComponent* component, time_t begin, @@ -461,6 +524,7 @@ private: (status != ICAL_STATUS_COMPLETED) && (status != ICAL_STATUS_CANCELLED)) { + ECalComponentAlarmAction omit[] = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 Appointment appointment; ECalComponentText text; @@ -475,47 +539,76 @@ private: appointment.uid = uid; appointment.type = type; - // Look through all of this component's alarms - // for DISPLAY or AUDIO url attachments. - // If we find any, use them for appointment.url and audio_sound - auto alarm_uids = e_cal_component_get_alarm_uids(component); - for(auto walk=alarm_uids; appointment.url.empty() && walk!=nullptr; walk=walk->next) + icalcomponent * icc = e_cal_component_get_icalcomponent(component); + g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak + + auto e_alarms = e_cal_util_generate_alarms_for_comp(component, + subtask->begin, + subtask->end, + omit, + e_cal_client_resolve_tzid_cb, + subtask->client, + subtask->default_timezone); + + std::map alarms; + + if (e_alarms != nullptr) { - auto alarm = e_cal_component_get_alarm(component, static_cast(walk->data)); + for (auto l=e_alarms->alarms; l!=nullptr; l=l->next) + { + auto ai = static_cast(l->data); + auto a = e_cal_component_get_alarm(component, ai->auid); - ECalComponentAlarmAction action; - e_cal_component_alarm_get_action(alarm, &action); - if ((action == E_CAL_COMPONENT_ALARM_DISPLAY) || (action == E_CAL_COMPONENT_ALARM_AUDIO)) + if (a != nullptr) + { + const DateTime alarm_begin{ai->occur_start}; + auto& alarm = alarms[alarm_begin]; + + if (alarm.text.empty()) + alarm.text = get_alarm_text(a); + if (alarm.audio_url.empty()) + alarm.audio_url = get_alarm_sound_url(a); + if (!alarm.time.is_set()) + alarm.time = alarm_begin; + if (alarm.duration == std::chrono::seconds::zero()) + alarm.duration = std::chrono::seconds(ai->occur_end - ai->occur_start); + + e_cal_component_alarm_free(a); + } + } + + e_cal_component_alarms_free(e_alarms); + } + // hm, no trigger. if this came from ubuntu-clock-app, + // manually add a single trigger for the todo event's time + else if (appointment.is_ubuntu_alarm()) + { + Alarm tmp; + tmp.time = appointment.begin; + + auto auids = e_cal_component_get_alarm_uids(component); + for(auto l=auids; l!=nullptr; l=l->next) { - icalattach* attach = nullptr; - e_cal_component_alarm_get_attach(alarm, &attach); - if (attach != nullptr) + const auto auid = static_cast(l->data); + auto a = e_cal_component_get_alarm(component, auid); + if (a != nullptr) { - if (icalattach_get_is_url (attach)) - { - const char* url = icalattach_get_url(attach); - if (url != nullptr) - { - if ((action == E_CAL_COMPONENT_ALARM_DISPLAY) && appointment.url.empty()) - { - appointment.url = url; - } - else if ((action == E_CAL_COMPONENT_ALARM_AUDIO) && appointment.audio_url.empty()) - { - appointment.audio_url = url; - } - } - } - - icalattach_unref(attach); + if (tmp.text.empty()) + tmp.text = get_alarm_text(a); + if (tmp.audio_url.empty()) + tmp.audio_url = get_alarm_sound_url(a); + e_cal_component_alarm_free(a); } } + cal_obj_uid_list_free(auids); - e_cal_component_alarm_free(alarm); + alarms[tmp.time] = tmp; } - cal_obj_uid_list_free(alarm_uids); - g_debug("adding appointment '%s' '%s'", appointment.summary.c_str(), appointment.url.c_str()); + appointment.alarms.reserve(alarms.size()); + for (const auto& it : alarms) + appointment.alarms.push_back(it.second); + subtask->task->appointments.push_back(appointment); } } diff --git a/src/main.cpp b/src/main.cpp index 9aa502c..38e5caa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -138,11 +138,13 @@ main(int /*argc*/, char** /*argv*/) auto notification_engine = std::make_shared("indicator-datetime-service"); std::unique_ptr snap (new Snap(notification_engine, state->settings)); auto alarm_queue = create_simple_alarm_queue(state->clock, snooze_planner, engine, timezone_); - auto on_snooze = [snooze_planner](const Appointment& a) {snooze_planner->add(a);}; - auto on_ok = [](const Appointment&){}; - auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& a) { - (*snap)(a, on_snooze, on_ok); - engine->disable_ubuntu_alarm(a); + auto on_snooze = [snooze_planner](const Appointment& appointment, const Alarm&) {snooze_planner->add(appointment);}; + auto on_ok = [](const Appointment&, const Alarm&){}; + auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& appointment) { +#warning placeholder; next step is to have AlarmQueue pass an appointment and alarm + Alarm alarm; + (*snap)(appointment, alarm, on_snooze, on_ok); + engine->disable_ubuntu_alarm(appointment); }; alarm_queue->alarm_reached().connect(on_alarm_reached); diff --git a/src/snap.cpp b/src/snap.cpp index e655d2d..ae0a62a 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -79,6 +79,7 @@ public: } void operator()(const Appointment& appointment, + const Alarm& alarm, appointment_func snooze, appointment_func ok) { @@ -96,7 +97,7 @@ public: if (appointment.is_ubuntu_alarm() || !silent_mode()) { // create the sound. const auto role = appointment.is_ubuntu_alarm() ? "alarm" : "alert"; - const auto uri = get_alarm_uri(appointment, m_settings); + const auto uri = get_alarm_uri(alarm, m_settings); const auto volume = m_settings->alarm_volume.get(); const bool loop = interactive; sound = std::make_shared(role, uri, volume, loop); @@ -140,12 +141,12 @@ public: // add 'sound', 'haptic', and 'awake' objects to the capture so // they stay alive until the closed callback is called; i.e., // for the lifespan of the notficiation - b.set_closed_callback([appointment, snooze, ok, sound, awake, haptic] + b.set_closed_callback([appointment, alarm, snooze, ok, sound, awake, haptic] (const std::string& action){ if (action == "snooze") - snooze(appointment); + snooze(appointment, alarm); else - ok(appointment); + ok(appointment, alarm); }); const auto key = m_engine->show(b); @@ -180,12 +181,12 @@ private: && (accounts_service_sound_get_silent_mode(m_accounts_service_sound_proxy)); } - std::string get_alarm_uri(const Appointment& appointment, + std::string get_alarm_uri(const Alarm& alarm, const std::shared_ptr& settings) const { const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"}; - const std::string candidates[] = { appointment.audio_url, + const std::string candidates[] = { alarm.audio_url, settings->alarm_sound.get(), FALLBACK }; @@ -236,10 +237,11 @@ Snap::~Snap() void Snap::operator()(const Appointment& appointment, + const Alarm& alarm, appointment_func show, appointment_func ok) { - (*impl)(appointment, show, ok); + (*impl)(appointment, alarm, show, ok); } /*** diff --git a/tests/manual-test-snap.cpp b/tests/manual-test-snap.cpp index 22ef137..cbe79cd 100644 --- a/tests/manual-test-snap.cpp +++ b/tests/manual-test-snap.cpp @@ -67,18 +67,18 @@ int main(int argc, const char* argv[]) Appointment a; a.color = "green"; a.summary = "Alarm"; - a.url = "alarm:///hello-world"; a.uid = "D4B57D50247291478ED31DED17FF0A9838DED402"; a.type = Appointment::UBUNTU_ALARM; a.begin = DateTime::Local(2014, 12, 25, 0, 0, 0); a.end = a.begin.end_of_day(); + a.alarms.push_back(Alarm{"Alarm Text", "", a.begin, std::chrono::seconds::zero()}); auto loop = g_main_loop_new(nullptr, false); - auto on_snooze = [loop](const Appointment& appt){ - g_message("You clicked 'Snooze' for appt url '%s'", appt.url.c_str()); + auto on_snooze = [loop](const Appointment& appt, const Alarm&){ + g_message("You clicked 'Snooze' for appt url '%s'", appt.summary.c_str()); g_idle_add(quit_idle, loop); }; - auto on_ok = [loop](const Appointment&){ + auto on_ok = [loop](const Appointment&, const Alarm&){ g_message("You clicked 'OK'"); g_idle_add(quit_idle, loop); }; @@ -93,7 +93,7 @@ int main(int argc, const char* argv[]) auto notification_engine = std::make_shared("indicator-datetime-service"); Snap snap (notification_engine, settings); - snap(a, on_snooze, on_ok); + snap(a, a.alarms.front(), on_snooze, on_ok); g_main_loop_run(loop); g_main_loop_unref(loop); diff --git a/tests/test-live-actions.cpp b/tests/test-live-actions.cpp index 1a34511..4f84f25 100644 --- a/tests/test-live-actions.cpp +++ b/tests/test-live-actions.cpp @@ -319,10 +319,6 @@ TEST_F(LiveActionsFixture, PhoneOpenAppointment) a.type = Appointment::UBUNTU_ALARM; m_actions->phone_open_appointment(a); EXPECT_EQ(clock_app_url, m_live_actions->last_url); - - a.url = "appid://blah"; - m_actions->phone_open_appointment(a); - EXPECT_EQ(a.url, m_live_actions->last_url); } TEST_F(LiveActionsFixture, PhoneOpenCalendarApp) @@ -392,7 +388,6 @@ TEST_F(LiveActionsFixture, CalendarState) Appointment a1; a1.color = "green"; a1.summary = "write unit tests"; - a1.url = "http://www.ubuntu.com/"; a1.uid = "D4B57D50247291478ED31DED17FF0A9838DED402"; a1.begin = tomorrow_begin; a1.end = tomorrow_end; @@ -403,7 +398,6 @@ TEST_F(LiveActionsFixture, CalendarState) Appointment a2; a2.color = "orange"; a2.summary = "code review"; - a2.url = "http://www.ubuntu.com/"; a2.uid = "2756ff7de3745bbffd65d2e4779c37c7ca60d843"; a2.begin = ubermorgen_begin; a2.end = ubermorgen_end; diff --git a/tests/test-snap.cpp b/tests/test-snap.cpp index 3dd4501..2e29132 100644 --- a/tests/test-snap.cpp +++ b/tests/test-snap.cpp @@ -106,12 +106,12 @@ protected: // init the Appointment appt.color = "green"; appt.summary = "Alarm"; - appt.url = "alarm:///hello-world"; appt.uid = "D4B57D50247291478ED31DED17FF0A9838DED402"; appt.type = Appointment::EVENT; const auto christmas = DateTime::Local(2015,12,25,0,0,0); appt.begin = christmas.start_of_day(); appt.end = christmas.end_of_day(); + appt.alarms.push_back(Alarm{"Alarm Text", "", appt.begin, std::chrono::seconds::zero()}); service = dbus_test_service_new(nullptr); @@ -343,8 +343,8 @@ TEST_F(SnapFixture, InteractiveDuration) make_interactive(); // call the Snap Decision - auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);}; - snap(appt, func, func); + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; + snap(appt, appt.alarms.front(), func, func); // confirm that Notify got called once guint len = 0; @@ -393,8 +393,8 @@ TEST_F(SnapFixture, InhibitSleep) make_interactive(); // invoke the notification - auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);}; - (*snap)(appt, func, func); + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; + (*snap)(appt, appt.alarms.front(), func, func); wait_msec(1000); @@ -448,8 +448,8 @@ TEST_F(SnapFixture, ForceScreen) make_interactive(); // invoke the notification - auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);}; - (*snap)(appt, func, func); + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; + (*snap)(appt, appt.alarms.front(), func, func); wait_msec(1000); @@ -484,14 +484,14 @@ TEST_F(SnapFixture, HapticModes) { auto settings = std::make_shared(); auto ne = std::make_shared(APP_NAME); - auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);}; + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; GError * error = nullptr; // invoke a snap decision while haptic feedback is set to "pulse", // confirm that VibratePattern got called settings->alarm_haptic.set("pulse"); auto snap = new Snap (ne, settings); - (*snap)(appt, func, func); + (*snap)(appt, appt.alarms.front(), func, func); wait_msec(100); EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (haptic_mock, haptic_obj, @@ -506,7 +506,7 @@ TEST_F(SnapFixture, HapticModes) dbus_test_dbus_mock_object_clear_method_calls (haptic_mock, haptic_obj, &error); settings->alarm_haptic.set("none"); snap = new Snap (ne, settings); - (*snap)(appt, func, func); + (*snap)(appt, appt.alarms.front(), func, func); wait_msec(100); EXPECT_FALSE (dbus_test_dbus_mock_object_check_method_call (haptic_mock, haptic_obj, -- cgit v1.2.3 From eab70c8626906fcf98f5d5653a8ea1bebd2cfc89 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Fri, 3 Apr 2015 12:31:36 -0500 Subject: add override keyword to WakeupTimer --- include/datetime/wakeup-timer-mainloop.h | 4 ++-- include/datetime/wakeup-timer-powerd.h | 4 ++-- include/datetime/wakeup-timer.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/datetime/wakeup-timer-mainloop.h b/include/datetime/wakeup-timer-mainloop.h index 86da8cf..5acb150 100644 --- a/include/datetime/wakeup-timer-mainloop.h +++ b/include/datetime/wakeup-timer-mainloop.h @@ -41,8 +41,8 @@ class MainloopWakeupTimer: public WakeupTimer public: explicit MainloopWakeupTimer(const std::shared_ptr&); ~MainloopWakeupTimer(); - void set_wakeup_time (const DateTime&); - core::Signal<>& timeout(); + void set_wakeup_time (const DateTime&) override; + core::Signal<>& timeout() override; private: MainloopWakeupTimer(const MainloopWakeupTimer&) =delete; diff --git a/include/datetime/wakeup-timer-powerd.h b/include/datetime/wakeup-timer-powerd.h index f11febe..5237fb9 100644 --- a/include/datetime/wakeup-timer-powerd.h +++ b/include/datetime/wakeup-timer-powerd.h @@ -41,8 +41,8 @@ class PowerdWakeupTimer: public WakeupTimer public: explicit PowerdWakeupTimer(const std::shared_ptr&); ~PowerdWakeupTimer(); - void set_wakeup_time(const DateTime&); - core::Signal<>& timeout(); + void set_wakeup_time(const DateTime&) override; + core::Signal<>& timeout() override; private: PowerdWakeupTimer(const PowerdWakeupTimer&) =delete; diff --git a/include/datetime/wakeup-timer.h b/include/datetime/wakeup-timer.h index 0a9923c..3e5344c 100644 --- a/include/datetime/wakeup-timer.h +++ b/include/datetime/wakeup-timer.h @@ -41,7 +41,7 @@ public: WakeupTimer() =default; virtual ~WakeupTimer() =default; virtual void set_wakeup_time (const DateTime&) =0; - virtual core::Signal<>& timeout() = 0; + virtual core::Signal<>& timeout() =0; }; /*** -- cgit v1.2.3 From 6e4367c088eb9026f29295c650f8639e09423212 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Fri, 3 Apr 2015 12:36:46 -0500 Subject: add override keyword to Clock class --- include/datetime/clock-mock.h | 2 +- include/datetime/clock.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/datetime/clock-mock.h b/include/datetime/clock-mock.h index 84fd860..011d079 100644 --- a/include/datetime/clock-mock.h +++ b/include/datetime/clock-mock.h @@ -39,7 +39,7 @@ public: explicit MockClock(const DateTime& dt): m_localtime(dt) {} ~MockClock() =default; - DateTime localtime() const { return m_localtime; } + DateTime localtime() const override { return m_localtime; } void set_localtime(const DateTime& dt) { diff --git a/include/datetime/clock.h b/include/datetime/clock.h index 8745d24..4a0642f 100644 --- a/include/datetime/clock.h +++ b/include/datetime/clock.h @@ -76,7 +76,7 @@ class LiveClock: public Clock public: LiveClock (const std::shared_ptr& zones); virtual ~LiveClock(); - virtual DateTime localtime() const; + virtual DateTime localtime() const override; private: class Impl; -- cgit v1.2.3 From 8c7daeeb87abd0be1b96169da18baf018c4859c9 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Fri, 3 Apr 2015 13:11:39 -0500 Subject: add the new Alarm class as an argument to the alarm queue class --- include/datetime/alarm-queue-simple.h | 18 +-- include/datetime/alarm-queue.h | 2 +- include/datetime/date-time.h | 3 + include/datetime/planner-snooze.h | 3 +- src/alarm-queue-simple.cpp | 226 ++++++++++++++++++++-------------- src/date-time.cpp | 10 ++ src/main.cpp | 8 +- src/planner-snooze.cpp | 16 ++- tests/test-alarm-queue.cpp | 17 +-- 9 files changed, 178 insertions(+), 125 deletions(-) diff --git a/include/datetime/alarm-queue-simple.h b/include/datetime/alarm-queue-simple.h index d191aec..838d28a 100644 --- a/include/datetime/alarm-queue-simple.h +++ b/include/datetime/alarm-queue-simple.h @@ -20,6 +20,8 @@ #ifndef INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H #define INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H +#include // std::shared_ptr + #include #include #include @@ -39,20 +41,12 @@ public: const std::shared_ptr& upcoming_planner, const std::shared_ptr& timer); ~SimpleAlarmQueue(); - core::Signal& alarm_reached(); + core::Signal& alarm_reached() override; private: - void requeue(); - bool find_next_alarm(Appointment& setme) const; - std::vector find_current_alarms() const; - void check_alarms(); - - 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; + class Impl; + friend class Impl; + std::unique_ptr impl; }; diff --git a/include/datetime/alarm-queue.h b/include/datetime/alarm-queue.h index ea51957..98abd7a 100644 --- a/include/datetime/alarm-queue.h +++ b/include/datetime/alarm-queue.h @@ -45,7 +45,7 @@ class AlarmQueue public: AlarmQueue() =default; virtual ~AlarmQueue() =default; - virtual core::Signal& alarm_reached() = 0; + virtual core::Signal& alarm_reached() =0; }; /*** diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h index 7dfc207..7e19a9d 100644 --- a/include/datetime/date-time.h +++ b/include/datetime/date-time.h @@ -22,6 +22,7 @@ #include // GDateTime +#include #include // time_t #include // std::shared_ptr @@ -43,6 +44,8 @@ public: DateTime(GTimeZone* tz, GDateTime* dt); DateTime(GTimeZone* tz, int year, int month, int day, int hour, int minute, double seconds); DateTime& operator=(const DateTime& in); + DateTime& operator+=(const std::chrono::minutes&); + DateTime& operator+=(const std::chrono::seconds&); DateTime to_timezone(const std::string& zone) const; DateTime start_of_month() const; DateTime start_of_day() const; diff --git a/include/datetime/planner-snooze.h b/include/datetime/planner-snooze.h index e2619fa..fc08d27 100644 --- a/include/datetime/planner-snooze.h +++ b/include/datetime/planner-snooze.h @@ -39,9 +39,8 @@ public: SnoozePlanner(const std::shared_ptr&, const std::shared_ptr&); ~SnoozePlanner(); - void add(const Appointment&); - core::Property>& appointments() override; + void add(const Appointment&, const Alarm&); protected: class Impl; diff --git a/src/alarm-queue-simple.cpp b/src/alarm-queue-simple.cpp index f45e61a..db0fd21 100644 --- a/src/alarm-queue-simple.cpp +++ b/src/alarm-queue-simple.cpp @@ -20,134 +20,174 @@ #include #include +#include namespace unity { namespace indicator { namespace datetime { /*** -**** Public API +**** ***/ -SimpleAlarmQueue::SimpleAlarmQueue(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()) +class SimpleAlarmQueue::Impl { - 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([=]{ - 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); +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([=]{ + 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(); + }); - m_timer->timeout().connect([this](){ - g_debug("AlarmQueue %p calling requeue() due to timeout", this); requeue(); - }); - - requeue(); -} + } -SimpleAlarmQueue::~SimpleAlarmQueue() -{ -} + ~Impl() + { + } -core::Signal& SimpleAlarmQueue::alarm_reached() -{ - return m_alarm_reached; -} + core::Signal& alarm_reached() + { + return m_alarm_reached; + } -/*** -**** -***/ +private: -void SimpleAlarmQueue::requeue() -{ - // kick any current alarms - for (auto current : find_current_alarms()) + void requeue() { - const std::pair trig {current.uid, current.begin}; - m_triggered.insert(trig); - m_alarm_reached(current); + // kick any current alarms + for (const auto& appointment : m_planner->appointments().get()) + { + Alarm alarm; + if (appointment_get_current_alarm(appointment, alarm)) + { + m_triggered.insert(std::make_pair(appointment.uid, alarm.time)); + m_alarm_reached(appointment, alarm); + } + } + + // idle until the next alarm + Alarm next; + if (find_next_alarm(next)) + { + g_debug ("setting timer to wake up for next appointment '%s' at %s", + next.text.c_str(), + next.time.format("%F %T").c_str()); + + m_timer->set_wakeup_time(next.time); + } } - // idle until the next alarm - Appointment next; - if (find_next_alarm(next)) + // find the next alarm that will kick now or in the future + bool find_next_alarm(Alarm& setme) const { - g_debug ("setting timer to wake up for next appointment '%s' at %s", - next.summary.c_str(), - next.begin.format("%F %T").c_str()); + bool found = false; + Alarm best; + const auto now = m_clock->localtime(); + const auto beginning_of_minute = now.start_of_minute(); - m_timer->set_wakeup_time(next.begin); - } -} + const auto appointments = m_planner->appointments().get(); + g_debug ("planner has %zu appointments in it", (size_t)appointments.size()); -// 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.start_of_minute(); + for(const auto& appointment : appointments) + { + for(const auto& alarm : appointment.alarms) + { + const std::pair trig{appointment.uid, alarm.time}; + if (m_triggered.count(trig)) + continue; - const auto appointments = m_planner->appointments().get(); - g_debug ("planner has %zu appointments in it", (size_t)appointments.size()); + if (alarm.time < beginning_of_minute) // has this one already passed? + continue; - for(const auto& walk : appointments) - { - const std::pair trig {walk.uid, walk.begin}; - if (m_triggered.count(trig)) - continue; + if (found && (best.time < alarm.time)) // do we already have a better match? + continue; - if (walk.begin < beginning_of_minute) // has this one already passed? - continue; + best = alarm; + found = true; + } + } - if (found && (tmp.begin < walk.begin)) // do we already have a better match? - continue; + if (found) + setme = best; - tmp = walk; - found = true; + return found; } - if (found) - setme = tmp; - return found; -} + bool appointment_get_current_alarm(const Appointment& appointment, Alarm& setme) const + { + const auto now = m_clock->localtime(); -// find the alarm(s) that should kick right now -std::vector SimpleAlarmQueue::find_current_alarms() const -{ - std::vector appointments; + for (const auto& alarm : appointment.alarms) + { + const auto trig = std::make_pair(appointment.uid, alarm.time); + if (m_triggered.count(trig)) // did we already use this one? + continue; + + if (DateTime::is_same_minute(now, alarm.time)) + { + setme = alarm; + return true; + } + } - const auto now = m_clock->localtime(); + return false; + } - for(const auto& walk : m_planner->appointments().get()) - { - const std::pair 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); - } + 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 +***/ + - return appointments; +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(); } /*** diff --git a/src/date-time.cpp b/src/date-time.cpp index 689688c..ecfc971 100644 --- a/src/date-time.cpp +++ b/src/date-time.cpp @@ -55,6 +55,16 @@ DateTime& DateTime::operator=(const DateTime& that) return *this; } +DateTime& DateTime::operator+=(const std::chrono::minutes& minutes) +{ + return (*this = add_full(0, 0, 0, 0, minutes.count(), 0)); +} + +DateTime& DateTime::operator+=(const std::chrono::seconds& seconds) +{ + return (*this = add_full(0, 0, 0, 0, 0, seconds.count())); +} + DateTime::DateTime(time_t t) { auto gtz = g_time_zone_new_local(); diff --git a/src/main.cpp b/src/main.cpp index 38e5caa..907d49f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -138,11 +138,11 @@ main(int /*argc*/, char** /*argv*/) auto notification_engine = std::make_shared("indicator-datetime-service"); std::unique_ptr snap (new Snap(notification_engine, state->settings)); auto alarm_queue = create_simple_alarm_queue(state->clock, snooze_planner, engine, timezone_); - auto on_snooze = [snooze_planner](const Appointment& appointment, const Alarm&) {snooze_planner->add(appointment);}; + auto on_snooze = [snooze_planner](const Appointment& appointment, const Alarm& alarm) { + snooze_planner->add(appointment, alarm); + }; auto on_ok = [](const Appointment&, const Alarm&){}; - auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& appointment) { -#warning placeholder; next step is to have AlarmQueue pass an appointment and alarm - Alarm alarm; + auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& appointment, const Alarm& alarm) { (*snap)(appointment, alarm, on_snooze, on_ok); engine->disable_ubuntu_alarm(appointment); }; diff --git a/src/planner-snooze.cpp b/src/planner-snooze.cpp index 29d5f06..e4062d2 100644 --- a/src/planner-snooze.cpp +++ b/src/planner-snooze.cpp @@ -51,14 +51,18 @@ public: return m_appointments; } - void add(const Appointment& appt_in) + void add(const Appointment& appt_in, const Alarm& alarm) { + // make a copy of the appointment with only this alarm Appointment appt = appt_in; + appt.alarms.clear(); + appt.alarms.push_back(alarm); // reschedule the alarm to go off N minutes from now - const auto alarm_duration_secs = appt.end - appt.begin; - appt.begin = m_clock->localtime().add_full(0,0,0,0,m_settings->snooze_duration.get(),0); - appt.end = appt.begin.add_full(0,0,0,0,0,alarm_duration_secs); + const auto offset = std::chrono::minutes(m_settings->snooze_duration.get()); + appt.begin += offset; + appt.end += offset; + appt.alarms[0].time += offset; // give it a new ID gchar* uid = e_uid_new(); @@ -95,9 +99,9 @@ SnoozePlanner::~SnoozePlanner() } void -SnoozePlanner::add(const Appointment& appointment) +SnoozePlanner::add(const Appointment& appointment, const Alarm& alarm) { - impl->add(appointment); + impl->add(appointment, alarm); } core::Property>& diff --git a/tests/test-alarm-queue.cpp b/tests/test-alarm-queue.cpp index 3fdf787..fdab425 100644 --- a/tests/test-alarm-queue.cpp +++ b/tests/test-alarm-queue.cpp @@ -48,7 +48,7 @@ protected: m_range_planner.reset(new MockRangePlanner); m_upcoming.reset(new UpcomingPlanner(m_range_planner, m_state->clock->localtime())); m_watcher.reset(new SimpleAlarmQueue(m_state->clock, m_upcoming, m_wakeup_timer)); - m_watcher->alarm_reached().connect([this](const Appointment& appt){ + m_watcher->alarm_reached().connect([this](const Appointment& appt, const Alarm& /*alarm*/){ m_triggered.push_back(appt.uid); }); @@ -71,7 +71,7 @@ protected: const auto tomorrow_begin = now.add_days(1).start_of_day(); const auto tomorrow_end = tomorrow_begin.end_of_day(); - Appointment a1; // an alarm clock appointment + Appointment a1; // an ubuntu alarm a1.color = "red"; a1.summary = "Alarm"; a1.summary = "http://www.example.com/"; @@ -79,18 +79,20 @@ protected: a1.type = Appointment::UBUNTU_ALARM; a1.begin = tomorrow_begin; a1.end = tomorrow_end; + a1.alarms.push_back(Alarm{"Alarm Text", "", a1.begin, std::chrono::seconds::zero()}); const auto ubermorgen_begin = now.add_days(2).start_of_day(); const auto ubermorgen_end = ubermorgen_begin.end_of_day(); - Appointment a2; // a non-alarm appointment + Appointment a2; // something else a2.color = "green"; a2.summary = "Other Text"; a2.summary = "http://www.monkey.com/"; a2.uid = "monkey"; - a1.type = Appointment::EVENT; + a2.type = Appointment::EVENT; a2.begin = ubermorgen_begin; a2.end = ubermorgen_end; + a2.alarms.push_back(Alarm{"Alarm Text", "", a2.begin, std::chrono::seconds::zero()}); return std::vector({a1, a2}); } @@ -105,7 +107,7 @@ TEST_F(AlarmQueueFixture, AppointmentsChanged) // Add some appointments to the planner. // One of these matches our state's localtime, so that should get triggered. std::vector a = build_some_appointments(); - a[0].begin = m_state->clock->localtime(); + a[0].begin = a[0].alarms.front().time = m_state->clock->localtime(); m_range_planner->appointments().set(a); // Confirm that it got fired @@ -135,7 +137,8 @@ TEST_F(AlarmQueueFixture, MoreThanOne) { const auto now = m_state->clock->localtime(); std::vector a = build_some_appointments(); - a[0].begin = a[1].begin = now; + a[0].alarms.front().time = now; + a[1].alarms.front().time = now; m_range_planner->appointments().set(a); ASSERT_EQ(2, m_triggered.size()); @@ -151,7 +154,7 @@ TEST_F(AlarmQueueFixture, NoDuplicates) const std::vector appointments = build_some_appointments(); std::vector a; a.push_back(appointments[0]); - a[0].begin = now; + a[0].alarms.front().time = now; m_range_planner->appointments().set(a); ASSERT_EQ(1, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); -- cgit v1.2.3 From e1aba742725c76257bd6845c93d3ac9a14d32089 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Fri, 3 Apr 2015 13:57:46 -0500 Subject: in EdsEngine, use empty initializer lists in the new valarm code --- src/engine-eds.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 820d9d4..ecbee59 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -444,8 +444,7 @@ private: e_cal_component_alarm_get_action(alarm, &action); if (action == E_CAL_COMPONENT_ALARM_DISPLAY) { - ECalComponentText text; - text.value = nullptr; + ECalComponentText text {}; e_cal_component_alarm_get_description(alarm, &text); if (text.value) ret = text.value; @@ -527,8 +526,7 @@ private: ECalComponentAlarmAction omit[] = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 Appointment appointment; - ECalComponentText text; - text.value = nullptr; + ECalComponentText text {}; e_cal_component_get_summary(component, &text); if (text.value) appointment.summary = text.value; @@ -579,8 +577,8 @@ private: e_cal_component_alarms_free(e_alarms); } - // hm, no trigger. if this came from ubuntu-clock-app, - // manually add a single trigger for the todo event's time + // hm, no alarms? if this came from ubuntu-clock-app, + // manually add a single alarm for the todo event's time else if (appointment.is_ubuntu_alarm()) { Alarm tmp; @@ -684,10 +682,10 @@ private: std::set m_sources; std::map m_clients; std::map m_views; - GCancellable* m_cancellable = nullptr; - ESourceRegistry* m_source_registry = nullptr; - guint m_rebuild_tag = 0; - time_t m_rebuild_deadline = 0; + GCancellable* m_cancellable {}; + ESourceRegistry* m_source_registry {}; + guint m_rebuild_tag {}; + time_t m_rebuild_deadline {}; }; /*** -- cgit v1.2.3 From 62d68e6453c0ad69ff4d71099441a8151e9a9bea Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 5 Apr 2015 17:27:52 -0500 Subject: fix misuse of ECalComponentAlarmInstance's fields. --- include/datetime/appointment.h | 1 - src/appointment.cpp | 3 +-- src/engine-eds.cpp | 4 +--- tests/manual-test-snap.cpp | 2 +- tests/test-alarm-queue.cpp | 4 ++-- tests/test-snap.cpp | 2 +- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/include/datetime/appointment.h b/include/datetime/appointment.h index 337d9b8..e9c1bc2 100644 --- a/include/datetime/appointment.h +++ b/include/datetime/appointment.h @@ -39,7 +39,6 @@ struct Alarm std::string text; std::string audio_url; DateTime time; - std::chrono::seconds duration; bool operator== (const Alarm& that) const; }; diff --git a/src/appointment.cpp b/src/appointment.cpp index 236c5f4..1edd93c 100644 --- a/src/appointment.cpp +++ b/src/appointment.cpp @@ -31,8 +31,7 @@ bool Alarm::operator==(const Alarm& that) const { return (text==that.text) && (audio_url==that.audio_url) - && (this->time==that.time) - && (duration==that.duration); + && (this->time==that.time); } bool Appointment::operator==(const Appointment& that) const diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index ecbee59..856f190 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -559,7 +559,7 @@ private: if (a != nullptr) { - const DateTime alarm_begin{ai->occur_start}; + const DateTime alarm_begin{ai->trigger}; auto& alarm = alarms[alarm_begin]; if (alarm.text.empty()) @@ -568,8 +568,6 @@ private: alarm.audio_url = get_alarm_sound_url(a); if (!alarm.time.is_set()) alarm.time = alarm_begin; - if (alarm.duration == std::chrono::seconds::zero()) - alarm.duration = std::chrono::seconds(ai->occur_end - ai->occur_start); e_cal_component_alarm_free(a); } diff --git a/tests/manual-test-snap.cpp b/tests/manual-test-snap.cpp index cbe79cd..d04cf14 100644 --- a/tests/manual-test-snap.cpp +++ b/tests/manual-test-snap.cpp @@ -71,7 +71,7 @@ int main(int argc, const char* argv[]) a.type = Appointment::UBUNTU_ALARM; a.begin = DateTime::Local(2014, 12, 25, 0, 0, 0); a.end = a.begin.end_of_day(); - a.alarms.push_back(Alarm{"Alarm Text", "", a.begin, std::chrono::seconds::zero()}); + a.alarms.push_back(Alarm{"Alarm Text", "", a.begin}); auto loop = g_main_loop_new(nullptr, false); auto on_snooze = [loop](const Appointment& appt, const Alarm&){ diff --git a/tests/test-alarm-queue.cpp b/tests/test-alarm-queue.cpp index fdab425..aa35668 100644 --- a/tests/test-alarm-queue.cpp +++ b/tests/test-alarm-queue.cpp @@ -79,7 +79,7 @@ protected: a1.type = Appointment::UBUNTU_ALARM; a1.begin = tomorrow_begin; a1.end = tomorrow_end; - a1.alarms.push_back(Alarm{"Alarm Text", "", a1.begin, std::chrono::seconds::zero()}); + a1.alarms.push_back(Alarm{"Alarm Text", "", a1.begin}); const auto ubermorgen_begin = now.add_days(2).start_of_day(); const auto ubermorgen_end = ubermorgen_begin.end_of_day(); @@ -92,7 +92,7 @@ protected: a2.type = Appointment::EVENT; a2.begin = ubermorgen_begin; a2.end = ubermorgen_end; - a2.alarms.push_back(Alarm{"Alarm Text", "", a2.begin, std::chrono::seconds::zero()}); + a2.alarms.push_back(Alarm{"Alarm Text", "", a2.begin}); return std::vector({a1, a2}); } diff --git a/tests/test-snap.cpp b/tests/test-snap.cpp index 2e29132..46fbd10 100644 --- a/tests/test-snap.cpp +++ b/tests/test-snap.cpp @@ -111,7 +111,7 @@ protected: const auto christmas = DateTime::Local(2015,12,25,0,0,0); appt.begin = christmas.start_of_day(); appt.end = christmas.end_of_day(); - appt.alarms.push_back(Alarm{"Alarm Text", "", appt.begin, std::chrono::seconds::zero()}); + appt.alarms.push_back(Alarm{"Alarm Text", "", appt.begin}); service = dbus_test_service_new(nullptr); -- cgit v1.2.3 From 32de6c5e866527c03e015f2c691837ea7800290e Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 5 Apr 2015 18:26:37 -0500 Subject: make DateTime::is_same_day() faster --- src/date-time.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/date-time.cpp b/src/date-time.cpp index ecfc971..54601d0 100644 --- a/src/date-time.cpp +++ b/src/date-time.cpp @@ -254,10 +254,12 @@ bool DateTime::is_same_day(const DateTime& a, const DateTime& b) if (!a.m_dt || !b.m_dt) return false; - const auto adt = a.get(); - const auto bdt = b.get(); - return (g_date_time_get_year(adt) == g_date_time_get_year(bdt)) - && (g_date_time_get_day_of_year(adt) == g_date_time_get_day_of_year(bdt)); + int ay, am, ad; + int by, bm, bd; + g_date_time_get_ymd(a.get(), &ay, &am, &ad); + g_date_time_get_ymd(b.get(), &by, &bm, &bd); + + return (ay==by) && (am==bm) && (ad==bd); } bool DateTime::is_same_minute(const DateTime& a, const DateTime& b) -- cgit v1.2.3 From 242bab5395d808b0c25b2df8971c11a5f8932001 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 5 Apr 2015 18:27:08 -0500 Subject: in SimpleAlarmQueue, use references instead of copies when copies aren't necessary --- src/alarm-queue-simple.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/alarm-queue-simple.cpp b/src/alarm-queue-simple.cpp index db0fd21..ae7722c 100644 --- a/src/alarm-queue-simple.cpp +++ b/src/alarm-queue-simple.cpp @@ -110,14 +110,14 @@ private: const auto now = m_clock->localtime(); const auto beginning_of_minute = now.start_of_minute(); - const auto appointments = m_planner->appointments().get(); + const auto& appointments = m_planner->appointments().get(); g_debug ("planner has %zu appointments in it", (size_t)appointments.size()); for(const auto& appointment : appointments) { for(const auto& alarm : appointment.alarms) { - const std::pair trig{appointment.uid, alarm.time}; + const std::pair trig{appointment.uid, alarm.time}; if (m_triggered.count(trig)) continue; @@ -145,7 +145,7 @@ private: for (const auto& alarm : appointment.alarms) { - const auto trig = std::make_pair(appointment.uid, alarm.time); + const std::pair trig{appointment.uid, alarm.time}; if (m_triggered.count(trig)) // did we already use this one? continue; -- cgit v1.2.3 From a0a6516e74f3b400058af01babfba61bdb1516eb Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 5 Apr 2015 19:19:01 -0500 Subject: add EDS tests to confirm valarm attachments are loaded properly and trigger in the AlarmQueue --- debian/control | 1 + tests/CMakeLists.txt | 37 ++++++- tests/run-eds-test.sh | 48 +++++++++ .../.config/evolution/sources/system-proxy.source | 21 ++++ .../share/evolution/calendar/system/calendar.ics | 47 +++++++++ tests/test-eds-valarms.cpp | 113 +++++++++++++++++++++ tests/timezone-mock.h | 1 + tests/wakeup-timer-mock.h | 78 ++++++++++++++ 8 files changed, 342 insertions(+), 4 deletions(-) create mode 100755 tests/run-eds-test.sh create mode 100644 tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source create mode 100644 tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics create mode 100644 tests/test-eds-valarms.cpp create mode 100644 tests/wakeup-timer-mock.h diff --git a/debian/control b/debian/control index 4b5f893..1532ec8 100644 --- a/debian/control +++ b/debian/control @@ -24,6 +24,7 @@ Build-Depends: cmake, libproperties-cpp-dev, libdbustest1-dev, locales, + gvfs-daemons Standards-Version: 3.9.3 Homepage: https://launchpad.net/indicator-datetime # If you aren't a member of ~indicator-applet-developers but need to upload diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 25fe5dc..8b6ec5d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -39,12 +39,10 @@ include_directories (${DBUSTEST_INCLUDE_DIRS}) add_definitions (-DSANDBOX="${CMAKE_CURRENT_BINARY_DIR}") - function(add_test_by_name name) set (TEST_NAME ${name}) add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) add_test (${TEST_NAME} ${TEST_NAME}) - add_dependencies (${TEST_NAME} libindicatordatetimeservice) target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) endfunction() add_test_by_name(test-datetime) @@ -65,9 +63,41 @@ add_test_by_name(test-utils) set (TEST_NAME manual-test-snap) add_executable (${TEST_NAME} ${TEST_NAME}.cpp) -add_dependencies (${TEST_NAME} libindicatordatetimeservice) target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) +## +## EDS Tests +## + +find_program(DBUS_RUNNER dbus-test-runner) +find_program(EVOLUTION_CALENDAR_FACTORY evolution-calendar-factory PATHS /usr/lib/evolution/) +find_program(EVOLUTION_SOURCE_REGISTRY evolution-source-registry PATHS /usr/lib/evolution/) +find_program(GVFSD gvfsd PATHS /usr/lib/gvfs/) +OPTION(EVOLUTION_SOURCE_SERVICE_NAME "DBus name for source registry") +if(NOT EVOLUTION_SOURCE_SERVICE_NAME) + set(EVOLUTION_SOURCE_SERVICE_NAME "org.gnome.evolution.dataserver.Sources3") +endif() + +function(add_eds_test_by_name name) + set (TEST_NAME ${name}) + add_executable(${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) + target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) + add_test (${TEST_NAME} + ${CMAKE_CURRENT_SOURCE_DIR}/run-eds-test.sh + ${DBUS_RUNNER} # arg1: dbus-test-runner exec + ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME} # arg2: test executable path + ${TEST_NAME} # arg3: test name + ${EVOLUTION_CALENDAR_FACTORY} # arg4: evolution-calendar-factory exec + ${EVOLUTION_SOURCE_SERVICE_NAME} # arg5: dbus name for source registry + ${EVOLUTION_SOURCE_REGISTRY} # arg6: evolution-source-registry exec + ${GVFSD} # arg7: gvfsd exec + ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}-config-files) # arg8: canned config files +endfunction() +add_eds_test_by_name(test-eds-valarms) + + + + # disabling the timezone unit tests because they require # https://code.launchpad.net/~ted/dbus-test-runner/multi-interface-test/+merge/199724 # which hasn't landed yet. These can be re-enabled as soon as that lands. @@ -75,7 +105,6 @@ target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEP # set (TEST_NAME ${name}) # add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) # add_test (${TEST_NAME} ${TEST_NAME}) -# add_dependencies (${TEST_NAME} libindicatordatetimeservice) # target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${DBUSTEST_LIBRARIES} ${GTEST_LIBS}) #endfunction() #add_dbusmock_test_by_name(test-timezone-geoclue) diff --git a/tests/run-eds-test.sh b/tests/run-eds-test.sh new file mode 100755 index 0000000..6e6f575 --- /dev/null +++ b/tests/run-eds-test.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +echo ARG0=$0 # this script +echo ARG1=$1 # full executable path of dbus-test-runner +echo ARG2=$2 # full executable path of test app +echo ARG3=$3 # test name +echo ARG4=$4 # full executable path of evolution-calendar-factory +echo ARG5=$5 # bus service name of calendar factory +echo ARG6=$6 # full exectuable path of evolution-source-registry +echo ARG7=$7 # full executable path of gvfs +echo ARG8=$8 # config files + +export TEST_TMP_DIR=$(mktemp -p "${TMPDIR:-.}" -d $3-XXXXXXXXXX) || exit 1 +trap 'rm -rf $TEST_TMP_DIR' EXIT +echo "running test '$3' in ${TEST_TMP_DIR}" + +export QT_QPA_PLATFORM=minimal +export HOME=${TEST_TMP_DIR} +export XDG_RUNTIME_DIR=${TEST_TMP_DIR} +export XDG_CACHE_HOME=${TEST_TMP_DIR}/.cache +export XDG_CONFIG_HOME=${TEST_TMP_DIR}/.config +export XDG_DATA_HOME=${TEST_TMP_DIR}/.local/share +export XDG_DESKTOP_DIR=${TEST_TMP_DIR} +export XDG_DOCUMENTS_DIR=${TEST_TMP_DIR} +export XDG_DOWNLOAD_DIR=${TEST_TMP_DIR} +export XDG_MUSIC_DIR=${TEST_TMP_DIR} +export XDG_PICTURES_DIR=${TEST_TMP_DIR} +export XDG_PUBLICSHARE_DIR=${TEST_TMP_DIR} +export XDG_TEMPLATES_DIR=${TEST_TMP_DIR} +export XDG_VIDEOS_DIR=${TEST_TMP_DIR} +export QORGANIZER_EDS_DEBUG=On +export GIO_USE_VFS=local # needed to ensure GVFS shuts down cleanly after the test is over + +echo HOMEDIR=${HOME} +rm -rf ${XDG_DATA_HOME} + +# if there are canned config files for this test, move them into place now +if [ -d $8 ]; then + echo "copying files from $8 to $HOME" + cp --verbose --archive $8/. $HOME +fi + +$1 --keep-env --max-wait=90 \ +--task $2 --task-name $3 --wait-until-complete --wait-for=org.gnome.evolution.dataserver.Calendar4 \ +--task $4 --task-name "evolution" --wait-until-complete -r +#--task $6 --task-name "source-registry" --wait-for=org.gtk.vfs.Daemon -r \ +#--task $7 --task-name "gvfsd" -r +return $? diff --git a/tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source b/tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source new file mode 100644 index 0000000..4b2f666 --- /dev/null +++ b/tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source @@ -0,0 +1,21 @@ + +[Data Source] +DisplayName=Default Proxy Settings +Enabled=true +Parent= + +[Proxy] +Method=default +IgnoreHosts=localhost;127.0.0.0/8;::1; +AutoconfigUrl= +FtpHost= +FtpPort=0 +HttpAuthPassword= +HttpAuthUser= +HttpHost= +HttpPort=8080 +HttpUseAuth=false +HttpsHost= +HttpsPort=0 +SocksHost= +SocksPort=0 diff --git a/tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics b/tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics new file mode 100644 index 0000000..fe526f4 --- /dev/null +++ b/tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics @@ -0,0 +1,47 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2015-04-05T21:32:47.354433Z(2) +BEGIN:VEVENT +UID:20150405T213247Z-4371-32011-1698-1@ubuntu-phablet +DTSTAMP:20150405T213247Z +DTSTART:20150424T183500Z +DTEND:20150424T193554Z +X-LIC-ERROR;X-LIC-ERRORTYPE=VALUE-PARSE-ERROR:Can't parse as RECUR value + in RRULE property. Removing entire property: ERROR: No Value +SUMMARY:London Sprint Flight +CREATED:20150405T213247Z +LAST-MODIFIED:20150405T213247Z +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-2@ubuntu-phablet +ACTION:AUDIO +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +REPEAT:3 +DURATION:PT2M +END:VALARM +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-3@ubuntu-phablet +ACTION:DISPLAY +DESCRIPTION:Time to pack! +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +REPEAT:3 +DURATION:PT2M +END:VALARM +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-5@ubuntu-phablet +ACTION:AUDIO +TRIGGER;VALUE=DURATION;RELATED=START:-PT3H +REPEAT:3 +DURATION:PT2M +END:VALARM +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-6@ubuntu-phablet +ACTION:DISPLAY +DESCRIPTION:Go to the airport! +TRIGGER;VALUE=DURATION;RELATED=START:-PT3H +REPEAT:3 +DURATION:PT2M +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/tests/test-eds-valarms.cpp b/tests/test-eds-valarms.cpp new file mode 100644 index 0000000..f30539a --- /dev/null +++ b/tests/test-eds-valarms.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2015 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 +#include +#include +#include + +#include + +#include "glib-fixture.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + + +using namespace unity::indicator::datetime; + +class VAlarmFixture: public GlibFixture +{ +private: + + typedef GlibFixture super; + +protected: + + void SetUp() + { + super::SetUp(); + } + + void TearDown() + { + super::TearDown(); + } +}; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, MultipleAppointments) +{ + // start the EDS engine + auto engine = std::make_shared(); + + // make a planner that looks at the first half of 2015 in EDS + auto tz = std::make_shared("America/Chicago"); + auto planner = std::make_shared(engine, tz); + const auto range_begin = DateTime::Local(2015,1, 1, 0, 0, 0.0); + const auto range_end = DateTime::Local(2015,6,31,23,59,59.5); + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_debug("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector& appointments){ + g_debug("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + const auto appts = planner->appointments().get(); + ASSERT_EQ(1, appts.size()); + const auto& appt = appts.front(); + ASSERT_EQ(8, appt.alarms.size()); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime::Local(2015,4,23,13,35,0)}), appt.alarms[0]); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime::Local(2015,4,23,13,37,0)}), appt.alarms[1]); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime::Local(2015,4,23,13,39,0)}), appt.alarms[2]); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime::Local(2015,4,23,13,41,0)}), appt.alarms[3]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime::Local(2015,4,24,10,35,0)}), appt.alarms[4]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime::Local(2015,4,24,10,37,0)}), appt.alarms[5]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime::Local(2015,4,24,10,39,0)}), appt.alarms[6]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime::Local(2015,4,24,10,41,0)}), appt.alarms[7]); + + // now let's try this out with AlarmQueue... + // hook the planner up to a SimpleAlarmQueue and confirm that it triggers for each of the reminders + auto mock_clock = std::make_shared(range_begin); + std::shared_ptr clock = mock_clock; + std::shared_ptr wakeup_timer = std::make_shared(clock); + auto alarm_queue = std::make_shared(clock, planner, wakeup_timer); + int triggered_count = 0; + alarm_queue->alarm_reached().connect([&triggered_count, appt](const Appointment&, const Alarm& active_alarm) { + EXPECT_TRUE(std::find(appt.alarms.begin(), appt.alarms.end(), active_alarm) != appt.alarms.end()); + ++triggered_count; + }); + for (auto now=range_begin; nowset_localtime(now); + EXPECT_EQ(appt.alarms.size(), triggered_count); +} diff --git a/tests/timezone-mock.h b/tests/timezone-mock.h index 67584cb..55151aa 100644 --- a/tests/timezone-mock.h +++ b/tests/timezone-mock.h @@ -30,6 +30,7 @@ class MockTimezone: public Timezone { public: MockTimezone() =default; + explicit MockTimezone(const std::string& zone) {timezone.set(zone);} ~MockTimezone() =default; }; diff --git a/tests/wakeup-timer-mock.h b/tests/wakeup-timer-mock.h new file mode 100644 index 0000000..8a59c97 --- /dev/null +++ b/tests/wakeup-timer-mock.h @@ -0,0 +1,78 @@ +/* + * Copyright 2015 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 + */ + +#ifndef INDICATOR_DATETIME_WAKEUP_TIMER_MOCK_H +#define INDICATOR_DATETIME_WAKEUP_TIMER_MOCK_H + +#include +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +/** + * \brief A one-shot timer that emits a signal when its timeout is reached. + */ +class MockWakeupTimer: public WakeupTimer +{ +public: + explicit MockWakeupTimer(const std::shared_ptr& clock): + m_clock(clock) + { + m_clock->minute_changed.connect([this](){ + test_for_wakeup(); + }); + } + + virtual ~MockWakeupTimer() =default; + + virtual void set_wakeup_time (const DateTime& wakeup_time) override { + m_wakeup_time = wakeup_time; + test_for_wakeup(); + } + + virtual core::Signal<>& timeout() override { return m_timeout; } + +private: + + void test_for_wakeup() + { + if (DateTime::is_same_minute(m_clock->localtime(), m_wakeup_time)) + m_timeout(); + } + + core::Signal<> m_timeout; + const std::shared_ptr& m_clock; + DateTime m_wakeup_time; +}; + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_WAKEUP_TIMER_MOCK_H -- cgit v1.2.3 From 13999bfbed4916149af24be2dc504461c4bdf0e9 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 5 Apr 2015 21:10:53 -0500 Subject: in tests/run-eds-test.sh, improve the comments --- tests/run-eds-test.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/run-eds-test.sh b/tests/run-eds-test.sh index 6e6f575..1f020ef 100755 --- a/tests/run-eds-test.sh +++ b/tests/run-eds-test.sh @@ -10,10 +10,12 @@ echo ARG6=$6 # full exectuable path of evolution-source-registry echo ARG7=$7 # full executable path of gvfs echo ARG8=$8 # config files +# set up the tmpdir and tell the shell to purge it when we exit export TEST_TMP_DIR=$(mktemp -p "${TMPDIR:-.}" -d $3-XXXXXXXXXX) || exit 1 trap 'rm -rf $TEST_TMP_DIR' EXIT echo "running test '$3' in ${TEST_TMP_DIR}" +# set up the environment variables export QT_QPA_PLATFORM=minimal export HOME=${TEST_TMP_DIR} export XDG_RUNTIME_DIR=${TEST_TMP_DIR} @@ -40,6 +42,7 @@ if [ -d $8 ]; then cp --verbose --archive $8/. $HOME fi +# run dbus-test-runner $1 --keep-env --max-wait=90 \ --task $2 --task-name $3 --wait-until-complete --wait-for=org.gnome.evolution.dataserver.Calendar4 \ --task $4 --task-name "evolution" --wait-until-complete -r -- cgit v1.2.3 From 38a39c03d5b44c825afe0d10f67cc0802dcf2573 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 5 Apr 2015 21:12:01 -0500 Subject: remove some new bits that turned out to be unneeded after all --- include/datetime/appointment.h | 2 -- tests/test-eds-valarms.cpp | 24 ++---------------------- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/include/datetime/appointment.h b/include/datetime/appointment.h index e9c1bc2..e323c9c 100644 --- a/include/datetime/appointment.h +++ b/include/datetime/appointment.h @@ -22,9 +22,7 @@ #include -#include #include -#include #include namespace unity { diff --git a/tests/test-eds-valarms.cpp b/tests/test-eds-valarms.cpp index f30539a..e9e07d8 100644 --- a/tests/test-eds-valarms.cpp +++ b/tests/test-eds-valarms.cpp @@ -19,8 +19,6 @@ #include -#include - #include #include #include @@ -32,27 +30,8 @@ #include "timezone-mock.h" #include "wakeup-timer-mock.h" - using namespace unity::indicator::datetime; - -class VAlarmFixture: public GlibFixture -{ -private: - - typedef GlibFixture super; - -protected: - - void SetUp() - { - super::SetUp(); - } - - void TearDown() - { - super::TearDown(); - } -}; +using VAlarmFixture = GlibFixture; /*** **** @@ -83,6 +62,7 @@ TEST_F(VAlarmFixture, MultipleAppointments) wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); } + // the planner should match what we've got in the calendar.ics file const auto appts = planner->appointments().get(); ASSERT_EQ(1, appts.size()); const auto& appt = appts.front(); -- cgit v1.2.3 From 29ba7597614912c068737fad2146561c70ca2dc7 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 09:25:59 -0500 Subject: in debian/control, add evolution-data-server to the build-dep now that we're using it for live EDS tests. --- debian/control | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/debian/control b/debian/control index 1532ec8..e9f14ec 100644 --- a/debian/control +++ b/debian/control @@ -2,18 +2,10 @@ Source: indicator-datetime Section: misc Priority: optional Maintainer: Ubuntu Desktop Team -# g++-4.9: since we use c++11 features, explicitly select a g++ version -# to protect from ABI breaks in libstdc++ -# language-pack-en-base: needed so unit tests can use 12h and 24h locales Build-Depends: cmake, - g++-4.9, dbus, - dbus-test-runner, - python3-dbusmock, debhelper (>= 9), dh-translations, - language-pack-touch-en | language-pack-en-base, - libgtest-dev, libglib2.0-dev (>= 2.35.4), libnotify-dev (>= 0.7.6), libgstreamer1.0-dev, @@ -22,9 +14,19 @@ Build-Depends: cmake, libedataserver1.2-dev (>= 3.5), liburl-dispatcher1-dev, libproperties-cpp-dev, +# for safeguarding against ABI breaks in libstdc++, explicitly select a g++ version + g++-4.9, +# for the test harness: + libgtest-dev, libdbustest1-dev, + dbus-test-runner, + python3-dbusmock, +# for 12h/24h locale unit tests: locales, - gvfs-daemons + language-pack-touch-en | language-pack-en-base, +# for running live EDS tests: + evolution-data-server, + gvfs-daemons, Standards-Version: 3.9.3 Homepage: https://launchpad.net/indicator-datetime # If you aren't a member of ~indicator-applet-developers but need to upload -- cgit v1.2.3 From 453a09477ec1eb238d214f61f695a09a0d84949d Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 09:27:01 -0500 Subject: in new code, use std::array rather than C style arrays --- src/engine-eds.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 856f190..2b62fd1 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -25,6 +25,7 @@ #include #include // std::sort() +#include #include // time() #include #include @@ -523,7 +524,7 @@ private: (status != ICAL_STATUS_COMPLETED) && (status != ICAL_STATUS_CANCELLED)) { - ECalComponentAlarmAction omit[] = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 + std::array omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 Appointment appointment; ECalComponentText text {}; @@ -543,7 +544,7 @@ private: auto e_alarms = e_cal_util_generate_alarms_for_comp(component, subtask->begin, subtask->end, - omit, + omit.data(), e_cal_client_resolve_tzid_cb, subtask->client, subtask->default_timezone); -- cgit v1.2.3 From af81477b2f5d4ffc76c51c72bd3ac89a4c4cac64 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 10:20:49 -0500 Subject: in the unit tests, add a PrintTo function for Alarms so that GTest can represent it as a string --- tests/print-to.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ tests/test-eds-valarms.cpp | 1 + 2 files changed, 46 insertions(+) create mode 100644 tests/print-to.h diff --git a/tests/print-to.h b/tests/print-to.h new file mode 100644 index 0000000..78cf574 --- /dev/null +++ b/tests/print-to.h @@ -0,0 +1,45 @@ +/* + * Copyright 2015 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 + */ + +#ifndef INDICATOR_DATETIME_TESTS_PRINT_TO +#define INDICATOR_DATETIME_TESTS_PRINT_TO + +#include + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** PrintTo() functions for GTest to represent objects as strings +***/ + +void +PrintTo(const Alarm& alarm, std::ostream* os) +{ + *os << "{text:'" << alarm.text << "', audio_url:'" << alarm.audio_url << "', time:'"< #include "glib-fixture.h" +#include "print-to.h" #include "timezone-mock.h" #include "wakeup-timer-mock.h" -- cgit v1.2.3 From 4438c3a50d4c10d7516d736cb31ded01c57c791e Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 13:16:24 -0500 Subject: in DateTime class, make it harder to accidentally mix local and nonlocal timezones by replacing DateTime::DateTime(time_t) with two methods, DateTime::Local(time_t) and DateTime(GTimeZone*, time_t) --- include/datetime/date-time.h | 3 ++- src/date-time.cpp | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h index 7e19a9d..ea9ea36 100644 --- a/include/datetime/date-time.h +++ b/include/datetime/date-time.h @@ -37,10 +37,11 @@ class DateTime { public: static DateTime NowLocal(); + static DateTime Local(time_t); static DateTime Local(int year, int month, int day, int hour, int minute, double seconds); DateTime(); - explicit DateTime(time_t t); + DateTime(GTimeZone* tz, time_t t); DateTime(GTimeZone* tz, GDateTime* dt); DateTime(GTimeZone* tz, int year, int month, int day, int hour, int minute, double seconds); DateTime& operator=(const DateTime& in); diff --git a/src/date-time.cpp b/src/date-time.cpp index 54601d0..4930bf6 100644 --- a/src/date-time.cpp +++ b/src/date-time.cpp @@ -65,13 +65,13 @@ DateTime& DateTime::operator+=(const std::chrono::seconds& seconds) return (*this = add_full(0, 0, 0, 0, 0, seconds.count())); } -DateTime::DateTime(time_t t) +DateTime::DateTime(GTimeZone* gtz, time_t t) { - auto gtz = g_time_zone_new_local(); - auto gdt = g_date_time_new_from_unix_local(t); + auto utc = g_date_time_new_from_unix_utc(t); + auto gdt = g_date_time_to_timezone (utc, gtz); reset(gtz, gdt); - g_time_zone_unref(gtz); g_date_time_unref(gdt); + g_date_time_unref(utc); } DateTime DateTime::NowLocal() @@ -84,6 +84,16 @@ DateTime DateTime::NowLocal() return dt; } +DateTime DateTime::Local(time_t t) +{ + auto gtz = g_time_zone_new_local(); + auto gdt = g_date_time_new_from_unix_local(t); + DateTime dt(gtz, gdt); + g_time_zone_unref(gtz); + g_date_time_unref(gdt); + return dt; +} + DateTime DateTime::Local(int year, int month, int day, int hour, int minute, double seconds) { auto gtz = g_time_zone_new_local(); -- cgit v1.2.3 From a3c896c8dd8c82b1d622a3a1220df0870f84d573 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 13:16:42 -0500 Subject: in new EDS tests, use timezones consistently --- tests/test-eds-valarms.cpp | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/tests/test-eds-valarms.cpp b/tests/test-eds-valarms.cpp index b92d190..47f29e7 100644 --- a/tests/test-eds-valarms.cpp +++ b/tests/test-eds-valarms.cpp @@ -43,18 +43,22 @@ TEST_F(VAlarmFixture, MultipleAppointments) // start the EDS engine auto engine = std::make_shared(); + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Chicago"}; + auto tz = std::make_shared(zone_str); + auto gtz = g_time_zone_new(zone_str); + // make a planner that looks at the first half of 2015 in EDS - auto tz = std::make_shared("America/Chicago"); auto planner = std::make_shared(engine, tz); - const auto range_begin = DateTime::Local(2015,1, 1, 0, 0, 0.0); - const auto range_end = DateTime::Local(2015,6,31,23,59,59.5); + const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2015,6,31,23,59,59.5}; planner->range().set(std::make_pair(range_begin, range_end)); // give EDS a moment to load if (planner->appointments().get().empty()) { - g_debug("waiting a moment for EDS to load..."); + g_message("waiting a moment for EDS to load..."); auto on_appointments_changed = [this](const std::vector& appointments){ - g_debug("ah, they loaded"); + g_message("ah, they loaded"); if (!appointments.empty()) g_main_loop_quit(loop); }; @@ -68,14 +72,14 @@ TEST_F(VAlarmFixture, MultipleAppointments) ASSERT_EQ(1, appts.size()); const auto& appt = appts.front(); ASSERT_EQ(8, appt.alarms.size()); - EXPECT_EQ(Alarm({"Time to pack!", "", DateTime::Local(2015,4,23,13,35,0)}), appt.alarms[0]); - EXPECT_EQ(Alarm({"Time to pack!", "", DateTime::Local(2015,4,23,13,37,0)}), appt.alarms[1]); - EXPECT_EQ(Alarm({"Time to pack!", "", DateTime::Local(2015,4,23,13,39,0)}), appt.alarms[2]); - EXPECT_EQ(Alarm({"Time to pack!", "", DateTime::Local(2015,4,23,13,41,0)}), appt.alarms[3]); - EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime::Local(2015,4,24,10,35,0)}), appt.alarms[4]); - EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime::Local(2015,4,24,10,37,0)}), appt.alarms[5]); - EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime::Local(2015,4,24,10,39,0)}), appt.alarms[6]); - EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime::Local(2015,4,24,10,41,0)}), appt.alarms[7]); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,35,0)}), appt.alarms[0]); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,37,0)}), appt.alarms[1]); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,39,0)}), appt.alarms[2]); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,41,0)}), appt.alarms[3]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,35,0)}), appt.alarms[4]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,37,0)}), appt.alarms[5]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,39,0)}), appt.alarms[6]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,41,0)}), appt.alarms[7]); // now let's try this out with AlarmQueue... // hook the planner up to a SimpleAlarmQueue and confirm that it triggers for each of the reminders @@ -91,4 +95,7 @@ TEST_F(VAlarmFixture, MultipleAppointments) for (auto now=range_begin; nowset_localtime(now); EXPECT_EQ(appt.alarms.size(), triggered_count); + + // cleanup + g_time_zone_unref(gtz); } -- cgit v1.2.3 From 7e9b4645721cc76322198df3b55f4cdedc55c200 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 13:17:39 -0500 Subject: in new EDS code, use timezones consistently --- src/engine-eds.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 2b62fd1..073b39d 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -497,11 +497,16 @@ 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); + // get the timezone we want to use for generated Appointments/Alarms + const char * location = icaltimezone_get_location(subtask->default_timezone); + auto gtz = g_time_zone_new(location); + g_debug("timezone abbreviation is %s", g_time_zone_get_abbreviation (gtz, 0)); + + const DateTime begin_dt { gtz, begin }; + const DateTime end_dt { gtz, 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(), + begin_dt.format("%F %T %z").c_str(), + end_dt.format("%F %T %z").c_str(), uid, (int)status); @@ -560,7 +565,7 @@ private: if (a != nullptr) { - const DateTime alarm_begin{ai->trigger}; + const DateTime alarm_begin{gtz, ai->trigger}; auto& alarm = alarms[alarm_begin]; if (alarm.text.empty()) @@ -608,6 +613,8 @@ private: subtask->task->appointments.push_back(appointment); } + + g_time_zone_unref(gtz); } return G_SOURCE_CONTINUE; -- cgit v1.2.3 From 9977f16b1f6e76fc3ac79383a1259340497af4a2 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 13:18:44 -0500 Subject: in Actions, sync DateTime API use by calling DateTime::Local(time_t) instead of DateTime::DateTime(time_t) --- src/actions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 839c9cd..930e100 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -43,7 +43,7 @@ DateTime datetime_from_timet_variant(GVariant* v) t = g_variant_get_int64(v); if (t != 0) - return DateTime(t); + return DateTime::Local(t); else return DateTime::NowLocal(); } @@ -143,7 +143,7 @@ void on_calendar_activated(GSimpleAction * /*action*/, g_return_if_fail(t != 0); - auto dt = DateTime(t).start_of_day(); + auto dt = DateTime::Local(t).start_of_day(); static_cast(gself)->set_calendar_date(dt); } -- cgit v1.2.3 From 006858f8955ebeda75ce6cca152bbf42af7da3da Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 13:29:07 -0500 Subject: in SimpleAlarmQueue, reduce a lambda capture to only the fields it needs --- src/alarm-queue-simple.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alarm-queue-simple.cpp b/src/alarm-queue-simple.cpp index ae7722c..d04c0fc 100644 --- a/src/alarm-queue-simple.cpp +++ b/src/alarm-queue-simple.cpp @@ -47,7 +47,7 @@ public: requeue(); }); - m_clock->minute_changed.connect([=]{ + 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; -- cgit v1.2.3 From 81f4d9916f4915365e7e271354771b71e0ef2c38 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 14:17:26 -0500 Subject: in SimpleAlarmQueue, make the signature for find_next_alarm() and appointment_get_current_alarm() suck less. --- src/alarm-queue-simple.cpp | 50 +++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/src/alarm-queue-simple.cpp b/src/alarm-queue-simple.cpp index d04c0fc..19e5208 100644 --- a/src/alarm-queue-simple.cpp +++ b/src/alarm-queue-simple.cpp @@ -79,38 +79,37 @@ private: void requeue() { + const auto appointments = m_planner->appointments().get(); + const Alarm* alarm; + // kick any current alarms - for (const auto& appointment : m_planner->appointments().get()) + for (const auto& appointment : appointments) { - Alarm alarm; - if (appointment_get_current_alarm(appointment, alarm)) + if ((alarm = appointment_get_current_alarm(appointment))) { - m_triggered.insert(std::make_pair(appointment.uid, alarm.time)); - m_alarm_reached(appointment, alarm); + m_triggered.insert(std::make_pair(appointment.uid, alarm->time)); + m_alarm_reached(appointment, *alarm); } } // idle until the next alarm - Alarm next; - if (find_next_alarm(next)) + if ((alarm = find_next_alarm(appointments))) { g_debug ("setting timer to wake up for next appointment '%s' at %s", - next.text.c_str(), - next.time.format("%F %T").c_str()); + alarm->text.c_str(), + alarm->time.format("%F %T").c_str()); - m_timer->set_wakeup_time(next.time); + m_timer->set_wakeup_time(alarm->time); } } - // find the next alarm that will kick now or in the future - bool find_next_alarm(Alarm& setme) const + // return the next Alarm (if any) that will kick now or in the future + const Alarm* find_next_alarm(const std::vector& appointments) const { - bool found = false; - Alarm best; + const Alarm* best {}; const auto now = m_clock->localtime(); const auto beginning_of_minute = now.start_of_minute(); - const auto& appointments = m_planner->appointments().get(); g_debug ("planner has %zu appointments in it", (size_t)appointments.size()); for(const auto& appointment : appointments) @@ -124,22 +123,18 @@ private: if (alarm.time < beginning_of_minute) // has this one already passed? continue; - if (found && (best.time < alarm.time)) // do we already have a better match? + if (best && (best->time < alarm.time)) // do we already have a better match? continue; - best = alarm; - found = true; + best = &alarm; } } - if (found) - setme = best; - - return found; + return best; } - - bool appointment_get_current_alarm(const Appointment& appointment, Alarm& setme) const + // return the Appointment's current Alarm (if any) + const Alarm* appointment_get_current_alarm(const Appointment& appointment) const { const auto now = m_clock->localtime(); @@ -150,13 +145,10 @@ private: continue; if (DateTime::is_same_minute(now, alarm.time)) - { - setme = alarm; - return true; - } + return &alarm; } - return false; + return nullptr; } -- cgit v1.2.3 From 392788b1db2d60cd73cac12e58fbc4e6ce304d9e Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 14:26:24 -0500 Subject: in SimpleAlarmQueue, add a new method 'bool already_triggered() const' to reduce code overlapl between find_next_alarm() and appointment_get_current_alarm() --- src/alarm-queue-simple.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/alarm-queue-simple.cpp b/src/alarm-queue-simple.cpp index 19e5208..921e8d2 100644 --- a/src/alarm-queue-simple.cpp +++ b/src/alarm-queue-simple.cpp @@ -103,6 +103,12 @@ private: } } + 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 { @@ -116,8 +122,7 @@ private: { for(const auto& alarm : appointment.alarms) { - const std::pair trig{appointment.uid, alarm.time}; - if (m_triggered.count(trig)) + if (already_triggered(appointment, alarm)) continue; if (alarm.time < beginning_of_minute) // has this one already passed? @@ -139,14 +144,8 @@ private: const auto now = m_clock->localtime(); for (const auto& alarm : appointment.alarms) - { - const std::pair trig{appointment.uid, alarm.time}; - if (m_triggered.count(trig)) // did we already use this one? - continue; - - if (DateTime::is_same_minute(now, alarm.time)) + if (!already_triggered(appointment, alarm) && DateTime::is_same_minute(now, alarm.time)) return &alarm; - } return nullptr; } -- cgit v1.2.3 From 7feb72952529269b9d6e8be2e04e992acb8812c7 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 14:35:29 -0500 Subject: in EngineEds, make the ECalComponentAlarmAction 'omit' array a constexpr. --- src/engine-eds.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 073b39d..4435bdf 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -529,7 +529,7 @@ private: (status != ICAL_STATUS_COMPLETED) && (status != ICAL_STATUS_CANCELLED)) { - std::array omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 + constexpr std::array omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 Appointment appointment; ECalComponentText text {}; @@ -549,7 +549,7 @@ private: auto e_alarms = e_cal_util_generate_alarms_for_comp(component, subtask->begin, subtask->end, - omit.data(), + const_cast(omit.data()), e_cal_client_resolve_tzid_cb, subtask->client, subtask->default_timezone); -- cgit v1.2.3 From aa3ee9ce63f3fb92b548593d03dd427f59c33ce5 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 14:58:34 -0500 Subject: in the EDS engine, give a better explanation in the comments how we handle alarms with no triggers, and why --- src/engine-eds.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 4435bdf..474bac5 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -581,8 +581,10 @@ private: e_cal_component_alarms_free(e_alarms); } - // hm, no alarms? if this came from ubuntu-clock-app, - // manually add a single alarm for the todo event's time + // Hm, no alarm triggers? + // That's a bug in alarms created by some versions of ubuntu-ui-toolkit. + // If that's what's happening here, let's handle those alarms anyway + // by effectively injecting a TRIGGER;VALUE=DURATION;RELATED=START:PT0S else if (appointment.is_ubuntu_alarm()) { Alarm tmp; -- cgit v1.2.3 From 10c574cc26a4a55d63eed7f3b846efb4d766dd6b Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 6 Apr 2015 15:09:16 -0500 Subject: in tests/run-eds-test.sh, only delete the tmpdir if the test passed. --- tests/run-eds-test.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/run-eds-test.sh b/tests/run-eds-test.sh index 1f020ef..0183350 100755 --- a/tests/run-eds-test.sh +++ b/tests/run-eds-test.sh @@ -11,8 +11,7 @@ echo ARG7=$7 # full executable path of gvfs echo ARG8=$8 # config files # set up the tmpdir and tell the shell to purge it when we exit -export TEST_TMP_DIR=$(mktemp -p "${TMPDIR:-.}" -d $3-XXXXXXXXXX) || exit 1 -trap 'rm -rf $TEST_TMP_DIR' EXIT +export TEST_TMP_DIR=$(mktemp -p "${TMPDIR:-/tmp}" -d $3-XXXXXXXXXX) || exit 1 echo "running test '$3' in ${TEST_TMP_DIR}" # set up the environment variables @@ -48,4 +47,11 @@ $1 --keep-env --max-wait=90 \ --task $4 --task-name "evolution" --wait-until-complete -r #--task $6 --task-name "source-registry" --wait-for=org.gtk.vfs.Daemon -r \ #--task $7 --task-name "gvfsd" -r -return $? +rv=$? + +# if the test passed, blow away the tmpdir +if [ $rv -eq 0 ]; then + rm -rf $TEST_TMP_DIR +fi + +return $rv -- cgit v1.2.3 From 49f49d4c18c2cc77a1a305c93a74e9e8ec903526 Mon Sep 17 00:00:00 2001 From: CI Train Bot Date: Mon, 6 Apr 2015 23:35:46 +0000 Subject: Releasing 13.10.0+15.04.20150406-0ubuntu1 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index 62a5b4e..4ec36ff 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +indicator-datetime (13.10.0+15.04.20150406-0ubuntu1) vivid; urgency=medium + + [ Charles Kerr ] + * Improve valarm support to honor calendar events' valarm triggers. + (LP: #1419001) + + -- CI Train Bot Mon, 06 Apr 2015 23:35:46 +0000 + indicator-datetime (13.10.0+15.04.20150331-0ubuntu1) vivid; urgency=medium [ Charles Kerr ] -- cgit v1.2.3