Merge lp:~charlesk/indicator-datetime/lp-1419001-honor-ical-valarms into lp:indicator-datetime/15.04

Proposed by Charles Kerr
Status: Merged
Approved by: Ted Gould
Approved revision: 430
Merged at revision: 408
Proposed branch: lp:~charlesk/indicator-datetime/lp-1419001-honor-ical-valarms
Merge into: lp:indicator-datetime/15.04
Diff against target: 1791 lines (+834/-269)
33 files modified
debian/control (+11/-8)
include/datetime/alarm-queue-simple.h (+6/-12)
include/datetime/alarm-queue.h (+1/-1)
include/datetime/appointment.h (+19/-4)
include/datetime/clock-mock.h (+1/-1)
include/datetime/clock.h (+1/-1)
include/datetime/date-time.h (+5/-1)
include/datetime/planner-snooze.h (+1/-2)
include/datetime/snap.h (+2/-1)
include/datetime/wakeup-timer-mainloop.h (+2/-2)
include/datetime/wakeup-timer-powerd.h (+2/-2)
include/datetime/wakeup-timer.h (+1/-1)
src/actions-live.cpp (+13/-6)
src/actions.cpp (+2/-2)
src/alarm-queue-simple.cpp (+140/-109)
src/appointment.cpp (+9/-3)
src/date-time.cpp (+31/-9)
src/engine-eds.cpp (+153/-54)
src/main.cpp (+7/-5)
src/planner-snooze.cpp (+10/-6)
src/snap.cpp (+9/-7)
tests/CMakeLists.txt (+33/-4)
tests/manual-test-snap.cpp (+5/-5)
tests/print-to.h (+45/-0)
tests/run-eds-test.sh (+57/-0)
tests/test-alarm-queue.cpp (+10/-7)
tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source (+21/-0)
tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics (+47/-0)
tests/test-eds-valarms.cpp (+101/-0)
tests/test-live-actions.cpp (+0/-6)
tests/test-snap.cpp (+10/-10)
tests/timezone-mock.h (+1/-0)
tests/wakeup-timer-mock.h (+78/-0)
To merge this branch: bzr merge lp:~charlesk/indicator-datetime/lp-1419001-honor-ical-valarms
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Ted Gould (community) Approve
Review via email: mp+255249@code.launchpad.net

Commit message

Improve valarm support to honor calendar events' valarm triggers.

Description of the change

== Description of the Change

Improve our valarm support for better calendar reminders, adding support for (1) multiple valarms per event, and (2) honor the valarms' trigger times, rather than unconditionally reminding when the event is reached.

A use case that exercises both of these features is an airplane flight vevent that has a "pack your bags" valarm set to be triggered the day before and a "go to the airport now" valarm to be triggered a few hours before.

At the code level, the Appointment class has been refactored to own a container of Alarm objects, which correspond to ical valarms. There are changes in the AlarmQueue and in the EDS backend to accommodate this.

The patch also adds EDS tests to confirm that we can get a set of Alarms correctly from an ical file loaded by evolution. A lot of the EDS/dbus-test-runner scaffolding is reused from renato's qtorganizer5-eds code.

== Checklist

> Are there any related MPs required for this MP to build/function as expected? Please list.

No other MPs needed

> Is your branch in sync with latest trunk? (e.g. bzr pull lp:trunk -> no changes)

Yes

> Did the code build without warnings?

Yes

> Did the tests run successfully?

Yes

> Did you perform an exploratory manual test run of your code change and any related functionality?

Yes

> If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?

N/A

> What device (or emulator) has your component test plan been executed successfully on?

Mako r164

> What manual tests are relevant for this MP?

indicator-datetime/calendar-event-reminder-time

> Did you include a link to the MR Review Checklist Template to make your reviewer's life easier?

https://wiki.ubuntu.com/Process/Merges/Checklists/indicator-datetime

To post a comment you must log in.
416. By Charles Kerr

in tests/run-eds-test.sh, improve the comments

417. By Charles Kerr

remove some new bits that turned out to be unneeded after all

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
418. By Charles Kerr

in debian/control, add evolution-data-server to the build-dep now that we're using it for live EDS tests.

419. By Charles Kerr

in new code, use std::array rather than C style arrays

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
420. By Charles Kerr

in the unit tests, add a PrintTo function for Alarms so that GTest can represent it as a string

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Ted Gould (ted) wrote :

Comments and a couple of questions, but I don't see any blockers.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
421. By Charles Kerr

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)

422. By Charles Kerr

in new EDS tests, use timezones consistently

423. By Charles Kerr

in new EDS code, use timezones consistently

424. By Charles Kerr

in Actions, sync DateTime API use by calling DateTime::Local(time_t) instead of DateTime::DateTime(time_t)

425. By Charles Kerr

in SimpleAlarmQueue, reduce a lambda capture to only the fields it needs

426. By Charles Kerr

in SimpleAlarmQueue, make the signature for find_next_alarm() and appointment_get_current_alarm() suck less.

427. By Charles Kerr

in SimpleAlarmQueue, add a new method 'bool already_triggered() const' to reduce code overlapl between find_next_alarm() and appointment_get_current_alarm()

428. By Charles Kerr

in EngineEds, make the ECalComponentAlarmAction 'omit' array a constexpr.

429. By Charles Kerr

in the EDS engine, give a better explanation in the comments how we handle alarms with no triggers, and why

430. By Charles Kerr

in tests/run-eds-test.sh, only delete the tmpdir if the test passed.

Revision history for this message
Charles Kerr (charlesk) wrote :

> You know you want to make these into a template :-)

Yes but not today. :-)

> Seems like you only need this here? No need to get references to the parameters.

Fixed r425

>> bool find_next_alarm(Alarm& setme) const
> I think in c++11 it makes more sense to return a tuple
> than to have a return in a parameter. Then you can bring
> it back with std::tie() in the caller to make it not suck

Returning a tuple of {success flag, Alarm} means we'd
still return an Alarm even if nothing was found.

Since we're walking through memory owned by the caller,
we could return a simple const pointer that's nullptr
if no match is found. That would make the API less screwy.

Fixed r426

> Seems like these two (the function above) could be combined
> into a shared function easily, just to reduce the number of
> search functions. If you "find_next_alarm()" and then is_same_minute
> the result I think that would provide more shared code.

They don't really fit, since one needs to loop through all appointments
and the other only walks through one appointment's alarms.
I could extract-method the "is this triggered already?" logic and
have both methods call it though.

Fixed r427

> Is there no #define for this? Scary. But we should probably static
> this array. No reason to build it on the stack each time.

Yeah, it's a strange interface.

Changed to a constexpr in r428.

> Not sure why we're checking the alarm to see if the text is set,
> why don't we always want the data structure to match what is
> being returned in a. Seems we want a sync here, even clearing
> the text if it was no longer in a.

There can be multiple valarms attached to an event, even to trigger
at the same time. e.g., events generated by calendar-app will have
one valarm specifying the sound action nd another specifying the
display action. We're iterating through all the valarms here, so we
have to be careful to pick up display text only if it's actually there.

> Confused at what this is doing, it seems like if the component
> didn't have any alarms, it also wouldn't have any UIDs for alarms
> that we could get text from.

It's not that it has no alarms; it's that the alarms had no triggers
specifying when they were supposed to go off. We need to special-case
that scenario because ubuntu-ui-toolkit used to generate alarms that
had no trigger.

Explained in comments better in r429.

> I think you actually only want to delete the temp directory if the
> test passes. That way it can end up still around so you can figure
> out what happened and/or picked up by Jenkins.

Good idea! Fixed r430.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2014-08-19 04:03:38 +0000
3+++ debian/control 2015-04-06 20:13:56 +0000
4@@ -2,18 +2,10 @@
5 Section: misc
6 Priority: optional
7 Maintainer: Ubuntu Desktop Team <ubuntu-desktop@lists.ubuntu.com>
8-# g++-4.9: since we use c++11 features, explicitly select a g++ version
9-# to protect from ABI breaks in libstdc++
10-# language-pack-en-base: needed so unit tests can use 12h and 24h locales
11 Build-Depends: cmake,
12- g++-4.9,
13 dbus,
14- dbus-test-runner,
15- python3-dbusmock,
16 debhelper (>= 9),
17 dh-translations,
18- language-pack-touch-en | language-pack-en-base,
19- libgtest-dev,
20 libglib2.0-dev (>= 2.35.4),
21 libnotify-dev (>= 0.7.6),
22 libgstreamer1.0-dev,
23@@ -22,8 +14,19 @@
24 libedataserver1.2-dev (>= 3.5),
25 liburl-dispatcher1-dev,
26 libproperties-cpp-dev,
27+# for safeguarding against ABI breaks in libstdc++, explicitly select a g++ version
28+ g++-4.9,
29+# for the test harness:
30+ libgtest-dev,
31 libdbustest1-dev,
32+ dbus-test-runner,
33+ python3-dbusmock,
34+# for 12h/24h locale unit tests:
35 locales,
36+ language-pack-touch-en | language-pack-en-base,
37+# for running live EDS tests:
38+ evolution-data-server,
39+ gvfs-daemons,
40 Standards-Version: 3.9.3
41 Homepage: https://launchpad.net/indicator-datetime
42 # If you aren't a member of ~indicator-applet-developers but need to upload
43
44=== modified file 'include/datetime/alarm-queue-simple.h'
45--- include/datetime/alarm-queue-simple.h 2014-06-10 16:56:32 +0000
46+++ include/datetime/alarm-queue-simple.h 2015-04-06 20:13:56 +0000
47@@ -20,6 +20,8 @@
48 #ifndef INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H
49 #define INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H
50
51+#include <memory> // std::shared_ptr
52+
53 #include <datetime/alarm-queue.h>
54 #include <datetime/clock.h>
55 #include <datetime/planner.h>
56@@ -39,20 +41,12 @@
57 const std::shared_ptr<Planner>& upcoming_planner,
58 const std::shared_ptr<WakeupTimer>& timer);
59 ~SimpleAlarmQueue();
60- core::Signal<const Appointment&>& alarm_reached();
61+ core::Signal<const Appointment&, const Alarm&>& alarm_reached() override;
62
63 private:
64- void requeue();
65- bool find_next_alarm(Appointment& setme) const;
66- std::vector<Appointment> find_current_alarms() const;
67- void check_alarms();
68-
69- std::set<std::pair<std::string,DateTime>> m_triggered;
70- const std::shared_ptr<Clock> m_clock;
71- const std::shared_ptr<Planner> m_planner;
72- const std::shared_ptr<WakeupTimer> m_timer;
73- core::Signal<const Appointment&> m_alarm_reached;
74- DateTime m_datetime;
75+ class Impl;
76+ friend class Impl;
77+ std::unique_ptr<Impl> impl;
78 };
79
80
81
82=== modified file 'include/datetime/alarm-queue.h'
83--- include/datetime/alarm-queue.h 2014-04-25 03:34:42 +0000
84+++ include/datetime/alarm-queue.h 2015-04-06 20:13:56 +0000
85@@ -45,7 +45,7 @@
86 public:
87 AlarmQueue() =default;
88 virtual ~AlarmQueue() =default;
89- virtual core::Signal<const Appointment&>& alarm_reached() = 0;
90+ virtual core::Signal<const Appointment&, const Alarm&>& alarm_reached() =0;
91 };
92
93 /***
94
95=== modified file 'include/datetime/appointment.h'
96--- include/datetime/appointment.h 2014-12-08 02:38:44 +0000
97+++ include/datetime/appointment.h 2015-04-06 20:13:56 +0000
98@@ -21,14 +21,28 @@
99 #define INDICATOR_DATETIME_APPOINTMENT_H
100
101 #include <datetime/date-time.h>
102+
103 #include <string>
104+#include <vector>
105
106 namespace unity {
107 namespace indicator {
108 namespace datetime {
109
110 /**
111- * \brief Plain Old Data Structure that represents a calendar appointment.
112+ * \brief Basic information required to raise a notification about some Appointment.
113+ */
114+struct Alarm
115+{
116+ std::string text;
117+ std::string audio_url;
118+ DateTime time;
119+
120+ bool operator== (const Alarm& that) const;
121+};
122+
123+/**
124+ * \brief An instance of an appointment; e.g. a calendar event or clock-app alarm
125 *
126 * @see Planner
127 */
128@@ -39,14 +53,15 @@
129 Type type = EVENT;
130 bool is_ubuntu_alarm() const { return type == UBUNTU_ALARM; }
131
132+ std::string uid;
133 std::string color;
134 std::string summary;
135- std::string url;
136- std::string uid;
137- std::string audio_url;
138+ std::string activation_url;
139 DateTime begin;
140 DateTime end;
141
142+ std::vector<Alarm> alarms;
143+
144 bool operator== (const Appointment& that) const;
145 };
146
147
148=== modified file 'include/datetime/clock-mock.h'
149--- include/datetime/clock-mock.h 2014-09-17 16:51:51 +0000
150+++ include/datetime/clock-mock.h 2015-04-06 20:13:56 +0000
151@@ -39,7 +39,7 @@
152 explicit MockClock(const DateTime& dt): m_localtime(dt) {}
153 ~MockClock() =default;
154
155- DateTime localtime() const { return m_localtime; }
156+ DateTime localtime() const override { return m_localtime; }
157
158 void set_localtime(const DateTime& dt)
159 {
160
161=== modified file 'include/datetime/clock.h'
162--- include/datetime/clock.h 2014-09-19 14:34:32 +0000
163+++ include/datetime/clock.h 2015-04-06 20:13:56 +0000
164@@ -76,7 +76,7 @@
165 public:
166 LiveClock (const std::shared_ptr<const Timezone>& zones);
167 virtual ~LiveClock();
168- virtual DateTime localtime() const;
169+ virtual DateTime localtime() const override;
170
171 private:
172 class Impl;
173
174=== modified file 'include/datetime/date-time.h'
175--- include/datetime/date-time.h 2015-03-16 20:07:54 +0000
176+++ include/datetime/date-time.h 2015-04-06 20:13:56 +0000
177@@ -22,6 +22,7 @@
178
179 #include <glib.h> // GDateTime
180
181+#include <chrono>
182 #include <ctime> // time_t
183 #include <memory> // std::shared_ptr
184
185@@ -36,13 +37,16 @@
186 {
187 public:
188 static DateTime NowLocal();
189+ static DateTime Local(time_t);
190 static DateTime Local(int year, int month, int day, int hour, int minute, double seconds);
191
192 DateTime();
193- explicit DateTime(time_t t);
194+ DateTime(GTimeZone* tz, time_t t);
195 DateTime(GTimeZone* tz, GDateTime* dt);
196 DateTime(GTimeZone* tz, int year, int month, int day, int hour, int minute, double seconds);
197 DateTime& operator=(const DateTime& in);
198+ DateTime& operator+=(const std::chrono::minutes&);
199+ DateTime& operator+=(const std::chrono::seconds&);
200 DateTime to_timezone(const std::string& zone) const;
201 DateTime start_of_month() const;
202 DateTime start_of_day() const;
203
204=== modified file 'include/datetime/planner-snooze.h'
205--- include/datetime/planner-snooze.h 2014-09-03 04:37:02 +0000
206+++ include/datetime/planner-snooze.h 2015-04-06 20:13:56 +0000
207@@ -39,9 +39,8 @@
208 SnoozePlanner(const std::shared_ptr<Settings>&,
209 const std::shared_ptr<Clock>&);
210 ~SnoozePlanner();
211- void add(const Appointment&);
212-
213 core::Property<std::vector<Appointment>>& appointments() override;
214+ void add(const Appointment&, const Alarm&);
215
216 protected:
217 class Impl;
218
219=== modified file 'include/datetime/snap.h'
220--- include/datetime/snap.h 2014-09-02 16:16:01 +0000
221+++ include/datetime/snap.h 2015-04-06 20:13:56 +0000
222@@ -42,8 +42,9 @@
223 const std::shared_ptr<const Settings>& settings);
224 virtual ~Snap();
225
226- typedef std::function<void(const Appointment&)> appointment_func;
227+ typedef std::function<void(const Appointment&, const Alarm&)> appointment_func;
228 void operator()(const Appointment& appointment,
229+ const Alarm& alarm,
230 appointment_func snooze,
231 appointment_func ok);
232
233
234=== modified file 'include/datetime/wakeup-timer-mainloop.h'
235--- include/datetime/wakeup-timer-mainloop.h 2014-09-17 16:51:51 +0000
236+++ include/datetime/wakeup-timer-mainloop.h 2015-04-06 20:13:56 +0000
237@@ -41,8 +41,8 @@
238 public:
239 explicit MainloopWakeupTimer(const std::shared_ptr<Clock>&);
240 ~MainloopWakeupTimer();
241- void set_wakeup_time (const DateTime&);
242- core::Signal<>& timeout();
243+ void set_wakeup_time (const DateTime&) override;
244+ core::Signal<>& timeout() override;
245
246 private:
247 MainloopWakeupTimer(const MainloopWakeupTimer&) =delete;
248
249=== modified file 'include/datetime/wakeup-timer-powerd.h'
250--- include/datetime/wakeup-timer-powerd.h 2014-09-17 16:51:51 +0000
251+++ include/datetime/wakeup-timer-powerd.h 2015-04-06 20:13:56 +0000
252@@ -41,8 +41,8 @@
253 public:
254 explicit PowerdWakeupTimer(const std::shared_ptr<Clock>&);
255 ~PowerdWakeupTimer();
256- void set_wakeup_time(const DateTime&);
257- core::Signal<>& timeout();
258+ void set_wakeup_time(const DateTime&) override;
259+ core::Signal<>& timeout() override;
260
261 private:
262 PowerdWakeupTimer(const PowerdWakeupTimer&) =delete;
263
264=== modified file 'include/datetime/wakeup-timer.h'
265--- include/datetime/wakeup-timer.h 2014-04-25 03:43:15 +0000
266+++ include/datetime/wakeup-timer.h 2015-04-06 20:13:56 +0000
267@@ -41,7 +41,7 @@
268 WakeupTimer() =default;
269 virtual ~WakeupTimer() =default;
270 virtual void set_wakeup_time (const DateTime&) =0;
271- virtual core::Signal<>& timeout() = 0;
272+ virtual core::Signal<>& timeout() =0;
273 };
274
275 /***
276
277=== modified file 'src/actions-live.cpp'
278--- src/actions-live.cpp 2015-03-16 16:51:47 +0000
279+++ src/actions-live.cpp 2015-04-06 20:13:56 +0000
280@@ -135,12 +135,19 @@
281
282 void LiveActions::phone_open_appointment(const Appointment& appt)
283 {
284- if (!appt.url.empty())
285- dispatch_url(appt.url);
286- else if (appt.is_ubuntu_alarm())
287- phone_open_alarm_app();
288- else
289- phone_open_calendar_app(DateTime::NowLocal());
290+ if (!appt.activation_url.empty())
291+ {
292+ dispatch_url(appt.activation_url);
293+ }
294+ else switch (appt.type)
295+ {
296+ case Appointment::UBUNTU_ALARM:
297+ phone_open_alarm_app();
298+ break;
299+
300+ default:
301+ phone_open_calendar_app(appt.begin);
302+ }
303 }
304
305 void LiveActions::phone_open_calendar_app(const DateTime&)
306
307=== modified file 'src/actions.cpp'
308--- src/actions.cpp 2015-03-15 03:42:06 +0000
309+++ src/actions.cpp 2015-04-06 20:13:56 +0000
310@@ -43,7 +43,7 @@
311 t = g_variant_get_int64(v);
312
313 if (t != 0)
314- return DateTime(t);
315+ return DateTime::Local(t);
316 else
317 return DateTime::NowLocal();
318 }
319@@ -143,7 +143,7 @@
320
321 g_return_if_fail(t != 0);
322
323- auto dt = DateTime(t).start_of_day();
324+ auto dt = DateTime::Local(t).start_of_day();
325 static_cast<Actions*>(gself)->set_calendar_date(dt);
326 }
327
328
329=== modified file 'src/alarm-queue-simple.cpp'
330--- src/alarm-queue-simple.cpp 2015-03-15 02:31:42 +0000
331+++ src/alarm-queue-simple.cpp 2015-04-06 20:13:56 +0000
332@@ -20,134 +20,165 @@
333 #include <datetime/alarm-queue-simple.h>
334
335 #include <cmath>
336+#include <set>
337
338 namespace unity {
339 namespace indicator {
340 namespace datetime {
341
342 /***
343+****
344+***/
345+
346+class SimpleAlarmQueue::Impl
347+{
348+public:
349+
350+ Impl(const std::shared_ptr<Clock>& clock,
351+ const std::shared_ptr<Planner>& planner,
352+ const std::shared_ptr<WakeupTimer>& timer):
353+ m_clock{clock},
354+ m_planner{planner},
355+ m_timer{timer},
356+ m_datetime{clock->localtime()}
357+ {
358+ m_planner->appointments().changed().connect([this](const std::vector<Appointment>&){
359+ g_debug("AlarmQueue %p calling requeue() due to appointments changed", this);
360+ requeue();
361+ });
362+
363+ m_clock->minute_changed.connect([this]{
364+ const auto now = m_clock->localtime();
365+ constexpr auto skew_threshold_usec = int64_t{90} * G_USEC_PER_SEC;
366+ const bool clock_jumped = std::abs(now - m_datetime) > skew_threshold_usec;
367+ m_datetime = now;
368+ if (clock_jumped) {
369+ g_debug("AlarmQueue %p calling requeue() due to clock skew", this);
370+ requeue();
371+ }
372+ });
373+
374+ m_timer->timeout().connect([this](){
375+ g_debug("AlarmQueue %p calling requeue() due to timeout", this);
376+ requeue();
377+ });
378+
379+ requeue();
380+ }
381+
382+ ~Impl()
383+ {
384+ }
385+
386+ core::Signal<const Appointment&, const Alarm&>& alarm_reached()
387+ {
388+ return m_alarm_reached;
389+ }
390+
391+private:
392+
393+ void requeue()
394+ {
395+ const auto appointments = m_planner->appointments().get();
396+ const Alarm* alarm;
397+
398+ // kick any current alarms
399+ for (const auto& appointment : appointments)
400+ {
401+ if ((alarm = appointment_get_current_alarm(appointment)))
402+ {
403+ m_triggered.insert(std::make_pair(appointment.uid, alarm->time));
404+ m_alarm_reached(appointment, *alarm);
405+ }
406+ }
407+
408+ // idle until the next alarm
409+ if ((alarm = find_next_alarm(appointments)))
410+ {
411+ g_debug ("setting timer to wake up for next appointment '%s' at %s",
412+ alarm->text.c_str(),
413+ alarm->time.format("%F %T").c_str());
414+
415+ m_timer->set_wakeup_time(alarm->time);
416+ }
417+ }
418+
419+ bool already_triggered (const Appointment& appt, const Alarm& alarm) const
420+ {
421+ const std::pair<const std::string&,const DateTime&> key{appt.uid, alarm.time};
422+ return m_triggered.count(key) != 0;
423+ }
424+
425+ // return the next Alarm (if any) that will kick now or in the future
426+ const Alarm* find_next_alarm(const std::vector<Appointment>& appointments) const
427+ {
428+ const Alarm* best {};
429+ const auto now = m_clock->localtime();
430+ const auto beginning_of_minute = now.start_of_minute();
431+
432+ g_debug ("planner has %zu appointments in it", (size_t)appointments.size());
433+
434+ for(const auto& appointment : appointments)
435+ {
436+ for(const auto& alarm : appointment.alarms)
437+ {
438+ if (already_triggered(appointment, alarm))
439+ continue;
440+
441+ if (alarm.time < beginning_of_minute) // has this one already passed?
442+ continue;
443+
444+ if (best && (best->time < alarm.time)) // do we already have a better match?
445+ continue;
446+
447+ best = &alarm;
448+ }
449+ }
450+
451+ return best;
452+ }
453+
454+ // return the Appointment's current Alarm (if any)
455+ const Alarm* appointment_get_current_alarm(const Appointment& appointment) const
456+ {
457+ const auto now = m_clock->localtime();
458+
459+ for (const auto& alarm : appointment.alarms)
460+ if (!already_triggered(appointment, alarm) && DateTime::is_same_minute(now, alarm.time))
461+ return &alarm;
462+
463+ return nullptr;
464+ }
465+
466+
467+ std::set<std::pair<std::string,DateTime>> m_triggered;
468+ const std::shared_ptr<Clock> m_clock;
469+ const std::shared_ptr<Planner> m_planner;
470+ const std::shared_ptr<WakeupTimer> m_timer;
471+ core::Signal<const Appointment&, const Alarm&> m_alarm_reached;
472+ DateTime m_datetime;
473+};
474+
475+/***
476 **** Public API
477 ***/
478
479+
480 SimpleAlarmQueue::SimpleAlarmQueue(const std::shared_ptr<Clock>& clock,
481 const std::shared_ptr<Planner>& planner,
482 const std::shared_ptr<WakeupTimer>& timer):
483- m_clock(clock),
484- m_planner(planner),
485- m_timer(timer),
486- m_datetime(clock->localtime())
487+ impl{new Impl{clock, planner, timer}}
488 {
489- m_planner->appointments().changed().connect([this](const std::vector<Appointment>&){
490- g_debug("AlarmQueue %p calling requeue() due to appointments changed", this);
491- requeue();
492- });
493-
494- m_clock->minute_changed.connect([=]{
495- const auto now = m_clock->localtime();
496- constexpr auto skew_threshold_usec = int64_t{90} * G_USEC_PER_SEC;
497- const bool clock_jumped = std::abs(now - m_datetime) > skew_threshold_usec;
498- m_datetime = now;
499- if (clock_jumped) {
500- g_debug("AlarmQueue %p calling requeue() due to clock skew", this);
501- requeue();
502- }
503- });
504-
505- m_timer->timeout().connect([this](){
506- g_debug("AlarmQueue %p calling requeue() due to timeout", this);
507- requeue();
508- });
509-
510- requeue();
511 }
512
513 SimpleAlarmQueue::~SimpleAlarmQueue()
514 {
515 }
516
517-core::Signal<const Appointment&>& SimpleAlarmQueue::alarm_reached()
518-{
519- return m_alarm_reached;
520-}
521-
522-/***
523-****
524-***/
525-
526-void SimpleAlarmQueue::requeue()
527-{
528- // kick any current alarms
529- for (auto current : find_current_alarms())
530- {
531- const std::pair<std::string,DateTime> trig {current.uid, current.begin};
532- m_triggered.insert(trig);
533- m_alarm_reached(current);
534- }
535-
536- // idle until the next alarm
537- Appointment next;
538- if (find_next_alarm(next))
539- {
540- g_debug ("setting timer to wake up for next appointment '%s' at %s",
541- next.summary.c_str(),
542- next.begin.format("%F %T").c_str());
543-
544- m_timer->set_wakeup_time(next.begin);
545- }
546-}
547-
548-// find the next alarm that will kick now or in the future
549-bool SimpleAlarmQueue::find_next_alarm(Appointment& setme) const
550-{
551- bool found = false;
552- Appointment tmp;
553- const auto now = m_clock->localtime();
554- const auto beginning_of_minute = now.start_of_minute();
555-
556- const auto appointments = m_planner->appointments().get();
557- g_debug ("planner has %zu appointments in it", (size_t)appointments.size());
558-
559- for(const auto& walk : appointments)
560- {
561- const std::pair<std::string,DateTime> trig {walk.uid, walk.begin};
562- if (m_triggered.count(trig))
563- continue;
564-
565- if (walk.begin < beginning_of_minute) // has this one already passed?
566- continue;
567-
568- if (found && (tmp.begin < walk.begin)) // do we already have a better match?
569- continue;
570-
571- tmp = walk;
572- found = true;
573- }
574-
575- if (found)
576- setme = tmp;
577-
578- return found;
579-}
580-
581-// find the alarm(s) that should kick right now
582-std::vector<Appointment> SimpleAlarmQueue::find_current_alarms() const
583-{
584- std::vector<Appointment> appointments;
585-
586- const auto now = m_clock->localtime();
587-
588- for(const auto& walk : m_planner->appointments().get())
589- {
590- const std::pair<std::string,DateTime> trig {walk.uid, walk.begin};
591- if (m_triggered.count(trig)) // did we already use this one?
592- continue;
593- if (!DateTime::is_same_minute(now, walk.begin))
594- continue;
595-
596- appointments.push_back(walk);
597- }
598-
599- return appointments;
600+core::Signal<const Appointment&, const Alarm&>&
601+SimpleAlarmQueue::alarm_reached()
602+{
603+ return impl->alarm_reached();
604 }
605
606 /***
607
608=== modified file 'src/appointment.cpp'
609--- src/appointment.cpp 2014-12-08 02:38:44 +0000
610+++ src/appointment.cpp 2015-04-06 20:13:56 +0000
611@@ -27,16 +27,22 @@
612 *****
613 ****/
614
615+bool Alarm::operator==(const Alarm& that) const
616+{
617+ return (text==that.text)
618+ && (audio_url==that.audio_url)
619+ && (this->time==that.time);
620+}
621+
622 bool Appointment::operator==(const Appointment& that) const
623 {
624 return (type==that.type)
625 && (uid==that.uid)
626 && (color==that.color)
627 && (summary==that.summary)
628- && (url==that.url)
629- && (audio_url==that.audio_url)
630 && (begin==that.begin)
631- && (end==that.end);
632+ && (end==that.end)
633+ && (alarms==that.alarms);
634 }
635
636 /****
637
638=== modified file 'src/date-time.cpp'
639--- src/date-time.cpp 2015-03-16 20:07:54 +0000
640+++ src/date-time.cpp 2015-04-06 20:13:56 +0000
641@@ -55,13 +55,23 @@
642 return *this;
643 }
644
645-DateTime::DateTime(time_t t)
646-{
647- auto gtz = g_time_zone_new_local();
648- auto gdt = g_date_time_new_from_unix_local(t);
649+DateTime& DateTime::operator+=(const std::chrono::minutes& minutes)
650+{
651+ return (*this = add_full(0, 0, 0, 0, minutes.count(), 0));
652+}
653+
654+DateTime& DateTime::operator+=(const std::chrono::seconds& seconds)
655+{
656+ return (*this = add_full(0, 0, 0, 0, 0, seconds.count()));
657+}
658+
659+DateTime::DateTime(GTimeZone* gtz, time_t t)
660+{
661+ auto utc = g_date_time_new_from_unix_utc(t);
662+ auto gdt = g_date_time_to_timezone (utc, gtz);
663 reset(gtz, gdt);
664- g_time_zone_unref(gtz);
665 g_date_time_unref(gdt);
666+ g_date_time_unref(utc);
667 }
668
669 DateTime DateTime::NowLocal()
670@@ -74,6 +84,16 @@
671 return dt;
672 }
673
674+DateTime DateTime::Local(time_t t)
675+{
676+ auto gtz = g_time_zone_new_local();
677+ auto gdt = g_date_time_new_from_unix_local(t);
678+ DateTime dt(gtz, gdt);
679+ g_time_zone_unref(gtz);
680+ g_date_time_unref(gdt);
681+ return dt;
682+}
683+
684 DateTime DateTime::Local(int year, int month, int day, int hour, int minute, double seconds)
685 {
686 auto gtz = g_time_zone_new_local();
687@@ -244,10 +264,12 @@
688 if (!a.m_dt || !b.m_dt)
689 return false;
690
691- const auto adt = a.get();
692- const auto bdt = b.get();
693- return (g_date_time_get_year(adt) == g_date_time_get_year(bdt))
694- && (g_date_time_get_day_of_year(adt) == g_date_time_get_day_of_year(bdt));
695+ int ay, am, ad;
696+ int by, bm, bd;
697+ g_date_time_get_ymd(a.get(), &ay, &am, &ad);
698+ g_date_time_get_ymd(b.get(), &by, &bm, &bd);
699+
700+ return (ay==by) && (am==bm) && (ad==bd);
701 }
702
703 bool DateTime::is_same_minute(const DateTime& a, const DateTime& b)
704
705=== modified file 'src/engine-eds.cpp'
706--- src/engine-eds.cpp 2015-01-15 19:15:27 +0000
707+++ src/engine-eds.cpp 2015-04-06 20:13:56 +0000
708@@ -25,6 +25,7 @@
709 #include <libedataserver/libedataserver.h>
710
711 #include <algorithm> // std::sort()
712+#include <array>
713 #include <ctime> // time()
714 #include <map>
715 #include <set>
716@@ -126,12 +127,18 @@
717 auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR);
718 const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension));
719 g_debug("calling e_cal_client_generate_instances for %p", (void*)client);
720+ auto subtask = new AppointmentSubtask(main_task,
721+ client,
722+ color,
723+ default_timezone,
724+ begin_timet,
725+ end_timet);
726 e_cal_client_generate_instances(client,
727 begin_timet,
728 end_timet,
729 m_cancellable,
730 my_get_appointments_foreach,
731- new AppointmentSubtask (main_task, client, color),
732+ subtask,
733 [](gpointer g){delete static_cast<AppointmentSubtask*>(g);});
734 }
735 }
736@@ -409,14 +416,70 @@
737 std::shared_ptr<Task> task;
738 ECalClient* client;
739 std::string color;
740- AppointmentSubtask(const std::shared_ptr<Task>& task_in, ECalClient* client_in, const char* color_in):
741- task(task_in), client(client_in)
742+ icaltimezone* default_timezone;
743+ time_t begin;
744+ time_t end;
745+
746+ AppointmentSubtask(const std::shared_ptr<Task>& task_in,
747+ ECalClient* client_in,
748+ const char* color_in,
749+ icaltimezone* default_tz,
750+ time_t begin_,
751+ time_t end_):
752+ task(task_in),
753+ client(client_in),
754+ default_timezone(default_tz),
755+ begin(begin_),
756+ end(end_)
757 {
758 if (color_in)
759 color = color_in;
760 }
761 };
762
763+ static std::string get_alarm_text(ECalComponentAlarm * alarm)
764+ {
765+ std::string ret;
766+
767+ ECalComponentAlarmAction action;
768+ e_cal_component_alarm_get_action(alarm, &action);
769+ if (action == E_CAL_COMPONENT_ALARM_DISPLAY)
770+ {
771+ ECalComponentText text {};
772+ e_cal_component_alarm_get_description(alarm, &text);
773+ if (text.value)
774+ ret = text.value;
775+ }
776+
777+ return ret;
778+ }
779+
780+ static std::string get_alarm_sound_url(ECalComponentAlarm * alarm)
781+ {
782+ std::string ret;
783+
784+ ECalComponentAlarmAction action;
785+ e_cal_component_alarm_get_action(alarm, &action);
786+ if (action == E_CAL_COMPONENT_ALARM_AUDIO)
787+ {
788+ icalattach* attach = nullptr;
789+ e_cal_component_alarm_get_attach(alarm, &attach);
790+ if (attach != nullptr)
791+ {
792+ if (icalattach_get_is_url (attach))
793+ {
794+ const char* url = icalattach_get_url(attach);
795+ if (url != nullptr)
796+ ret = url;
797+ }
798+
799+ icalattach_unref(attach);
800+ }
801+ }
802+
803+ return ret;
804+ }
805+
806 static gboolean
807 my_get_appointments_foreach(ECalComponent* component,
808 time_t begin,
809@@ -434,11 +497,16 @@
810 auto status = ICAL_STATUS_NONE;
811 e_cal_component_get_status(component, &status);
812
813- const auto begin_dt = DateTime(begin);
814- const auto end_dt = DateTime(end);
815+ // get the timezone we want to use for generated Appointments/Alarms
816+ const char * location = icaltimezone_get_location(subtask->default_timezone);
817+ auto gtz = g_time_zone_new(location);
818+ g_debug("timezone abbreviation is %s", g_time_zone_get_abbreviation (gtz, 0));
819+
820+ const DateTime begin_dt { gtz, begin };
821+ const DateTime end_dt { gtz, end };
822 g_debug ("got appointment from %s to %s, uid %s status %d",
823- begin_dt.format("%F %T").c_str(),
824- end_dt.format("%F %T").c_str(),
825+ begin_dt.format("%F %T %z").c_str(),
826+ end_dt.format("%F %T %z").c_str(),
827 uid,
828 (int)status);
829
830@@ -461,10 +529,10 @@
831 (status != ICAL_STATUS_COMPLETED) &&
832 (status != ICAL_STATUS_CANCELLED))
833 {
834+ constexpr std::array<ECalComponentAlarmAction,1> omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1
835 Appointment appointment;
836
837- ECalComponentText text;
838- text.value = nullptr;
839+ ECalComponentText text {};
840 e_cal_component_get_summary(component, &text);
841 if (text.value)
842 appointment.summary = text.value;
843@@ -475,49 +543,80 @@
844 appointment.uid = uid;
845 appointment.type = type;
846
847- // Look through all of this component's alarms
848- // for DISPLAY or AUDIO url attachments.
849- // If we find any, use them for appointment.url and audio_sound
850- auto alarm_uids = e_cal_component_get_alarm_uids(component);
851- for(auto walk=alarm_uids; appointment.url.empty() && walk!=nullptr; walk=walk->next)
852- {
853- auto alarm = e_cal_component_get_alarm(component, static_cast<const char*>(walk->data));
854-
855- ECalComponentAlarmAction action;
856- e_cal_component_alarm_get_action(alarm, &action);
857- if ((action == E_CAL_COMPONENT_ALARM_DISPLAY) || (action == E_CAL_COMPONENT_ALARM_AUDIO))
858- {
859- icalattach* attach = nullptr;
860- e_cal_component_alarm_get_attach(alarm, &attach);
861- if (attach != nullptr)
862- {
863- if (icalattach_get_is_url (attach))
864- {
865- const char* url = icalattach_get_url(attach);
866- if (url != nullptr)
867- {
868- if ((action == E_CAL_COMPONENT_ALARM_DISPLAY) && appointment.url.empty())
869- {
870- appointment.url = url;
871- }
872- else if ((action == E_CAL_COMPONENT_ALARM_AUDIO) && appointment.audio_url.empty())
873- {
874- appointment.audio_url = url;
875- }
876- }
877- }
878-
879- icalattach_unref(attach);
880- }
881- }
882-
883- e_cal_component_alarm_free(alarm);
884- }
885- cal_obj_uid_list_free(alarm_uids);
886-
887- g_debug("adding appointment '%s' '%s'", appointment.summary.c_str(), appointment.url.c_str());
888+ icalcomponent * icc = e_cal_component_get_icalcomponent(component);
889+ g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak
890+
891+ auto e_alarms = e_cal_util_generate_alarms_for_comp(component,
892+ subtask->begin,
893+ subtask->end,
894+ const_cast<ECalComponentAlarmAction*>(omit.data()),
895+ e_cal_client_resolve_tzid_cb,
896+ subtask->client,
897+ subtask->default_timezone);
898+
899+ std::map<DateTime,Alarm> alarms;
900+
901+ if (e_alarms != nullptr)
902+ {
903+ for (auto l=e_alarms->alarms; l!=nullptr; l=l->next)
904+ {
905+ auto ai = static_cast<ECalComponentAlarmInstance*>(l->data);
906+ auto a = e_cal_component_get_alarm(component, ai->auid);
907+
908+ if (a != nullptr)
909+ {
910+ const DateTime alarm_begin{gtz, ai->trigger};
911+ auto& alarm = alarms[alarm_begin];
912+
913+ if (alarm.text.empty())
914+ alarm.text = get_alarm_text(a);
915+ if (alarm.audio_url.empty())
916+ alarm.audio_url = get_alarm_sound_url(a);
917+ if (!alarm.time.is_set())
918+ alarm.time = alarm_begin;
919+
920+ e_cal_component_alarm_free(a);
921+ }
922+ }
923+
924+ e_cal_component_alarms_free(e_alarms);
925+ }
926+ // Hm, no alarm triggers?
927+ // That's a bug in alarms created by some versions of ubuntu-ui-toolkit.
928+ // If that's what's happening here, let's handle those alarms anyway
929+ // by effectively injecting a TRIGGER;VALUE=DURATION;RELATED=START:PT0S
930+ else if (appointment.is_ubuntu_alarm())
931+ {
932+ Alarm tmp;
933+ tmp.time = appointment.begin;
934+
935+ auto auids = e_cal_component_get_alarm_uids(component);
936+ for(auto l=auids; l!=nullptr; l=l->next)
937+ {
938+ const auto auid = static_cast<const char*>(l->data);
939+ auto a = e_cal_component_get_alarm(component, auid);
940+ if (a != nullptr)
941+ {
942+ if (tmp.text.empty())
943+ tmp.text = get_alarm_text(a);
944+ if (tmp.audio_url.empty())
945+ tmp.audio_url = get_alarm_sound_url(a);
946+ e_cal_component_alarm_free(a);
947+ }
948+ }
949+ cal_obj_uid_list_free(auids);
950+
951+ alarms[tmp.time] = tmp;
952+ }
953+
954+ appointment.alarms.reserve(alarms.size());
955+ for (const auto& it : alarms)
956+ appointment.alarms.push_back(it.second);
957+
958 subtask->task->appointments.push_back(appointment);
959 }
960+
961+ g_time_zone_unref(gtz);
962 }
963
964 return G_SOURCE_CONTINUE;
965@@ -591,10 +690,10 @@
966 std::set<ESource*> m_sources;
967 std::map<ESource*,ECalClient*> m_clients;
968 std::map<ESource*,ECalClientView*> m_views;
969- GCancellable* m_cancellable = nullptr;
970- ESourceRegistry* m_source_registry = nullptr;
971- guint m_rebuild_tag = 0;
972- time_t m_rebuild_deadline = 0;
973+ GCancellable* m_cancellable {};
974+ ESourceRegistry* m_source_registry {};
975+ guint m_rebuild_tag {};
976+ time_t m_rebuild_deadline {};
977 };
978
979 /***
980
981=== modified file 'src/main.cpp'
982--- src/main.cpp 2015-02-28 18:37:07 +0000
983+++ src/main.cpp 2015-04-06 20:13:56 +0000
984@@ -138,11 +138,13 @@
985 auto notification_engine = std::make_shared<uin::Engine>("indicator-datetime-service");
986 std::unique_ptr<Snap> snap (new Snap(notification_engine, state->settings));
987 auto alarm_queue = create_simple_alarm_queue(state->clock, snooze_planner, engine, timezone_);
988- auto on_snooze = [snooze_planner](const Appointment& a) {snooze_planner->add(a);};
989- auto on_ok = [](const Appointment&){};
990- auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& a) {
991- (*snap)(a, on_snooze, on_ok);
992- engine->disable_ubuntu_alarm(a);
993+ auto on_snooze = [snooze_planner](const Appointment& appointment, const Alarm& alarm) {
994+ snooze_planner->add(appointment, alarm);
995+ };
996+ auto on_ok = [](const Appointment&, const Alarm&){};
997+ auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& appointment, const Alarm& alarm) {
998+ (*snap)(appointment, alarm, on_snooze, on_ok);
999+ engine->disable_ubuntu_alarm(appointment);
1000 };
1001 alarm_queue->alarm_reached().connect(on_alarm_reached);
1002
1003
1004=== modified file 'src/planner-snooze.cpp'
1005--- src/planner-snooze.cpp 2014-09-17 16:51:51 +0000
1006+++ src/planner-snooze.cpp 2015-04-06 20:13:56 +0000
1007@@ -51,14 +51,18 @@
1008 return m_appointments;
1009 }
1010
1011- void add(const Appointment& appt_in)
1012+ void add(const Appointment& appt_in, const Alarm& alarm)
1013 {
1014+ // make a copy of the appointment with only this alarm
1015 Appointment appt = appt_in;
1016+ appt.alarms.clear();
1017+ appt.alarms.push_back(alarm);
1018
1019 // reschedule the alarm to go off N minutes from now
1020- const auto alarm_duration_secs = appt.end - appt.begin;
1021- appt.begin = m_clock->localtime().add_full(0,0,0,0,m_settings->snooze_duration.get(),0);
1022- appt.end = appt.begin.add_full(0,0,0,0,0,alarm_duration_secs);
1023+ const auto offset = std::chrono::minutes(m_settings->snooze_duration.get());
1024+ appt.begin += offset;
1025+ appt.end += offset;
1026+ appt.alarms[0].time += offset;
1027
1028 // give it a new ID
1029 gchar* uid = e_uid_new();
1030@@ -95,9 +99,9 @@
1031 }
1032
1033 void
1034-SnoozePlanner::add(const Appointment& appointment)
1035+SnoozePlanner::add(const Appointment& appointment, const Alarm& alarm)
1036 {
1037- impl->add(appointment);
1038+ impl->add(appointment, alarm);
1039 }
1040
1041 core::Property<std::vector<Appointment>>&
1042
1043=== modified file 'src/snap.cpp'
1044--- src/snap.cpp 2015-03-31 18:54:26 +0000
1045+++ src/snap.cpp 2015-04-06 20:13:56 +0000
1046@@ -79,6 +79,7 @@
1047 }
1048
1049 void operator()(const Appointment& appointment,
1050+ const Alarm& alarm,
1051 appointment_func snooze,
1052 appointment_func ok)
1053 {
1054@@ -96,7 +97,7 @@
1055 if (appointment.is_ubuntu_alarm() || !silent_mode()) {
1056 // create the sound.
1057 const auto role = appointment.is_ubuntu_alarm() ? "alarm" : "alert";
1058- const auto uri = get_alarm_uri(appointment, m_settings);
1059+ const auto uri = get_alarm_uri(alarm, m_settings);
1060 const auto volume = m_settings->alarm_volume.get();
1061 const bool loop = interactive;
1062 sound = std::make_shared<uin::Sound>(role, uri, volume, loop);
1063@@ -140,12 +141,12 @@
1064 // add 'sound', 'haptic', and 'awake' objects to the capture so
1065 // they stay alive until the closed callback is called; i.e.,
1066 // for the lifespan of the notficiation
1067- b.set_closed_callback([appointment, snooze, ok, sound, awake, haptic]
1068+ b.set_closed_callback([appointment, alarm, snooze, ok, sound, awake, haptic]
1069 (const std::string& action){
1070 if (action == "snooze")
1071- snooze(appointment);
1072+ snooze(appointment, alarm);
1073 else
1074- ok(appointment);
1075+ ok(appointment, alarm);
1076 });
1077
1078 const auto key = m_engine->show(b);
1079@@ -180,12 +181,12 @@
1080 && (accounts_service_sound_get_silent_mode(m_accounts_service_sound_proxy));
1081 }
1082
1083- std::string get_alarm_uri(const Appointment& appointment,
1084+ std::string get_alarm_uri(const Alarm& alarm,
1085 const std::shared_ptr<const Settings>& settings) const
1086 {
1087 const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"};
1088
1089- const std::string candidates[] = { appointment.audio_url,
1090+ const std::string candidates[] = { alarm.audio_url,
1091 settings->alarm_sound.get(),
1092 FALLBACK };
1093
1094@@ -236,10 +237,11 @@
1095
1096 void
1097 Snap::operator()(const Appointment& appointment,
1098+ const Alarm& alarm,
1099 appointment_func show,
1100 appointment_func ok)
1101 {
1102- (*impl)(appointment, show, ok);
1103+ (*impl)(appointment, alarm, show, ok);
1104 }
1105
1106 /***
1107
1108=== modified file 'tests/CMakeLists.txt'
1109--- tests/CMakeLists.txt 2015-03-16 20:07:54 +0000
1110+++ tests/CMakeLists.txt 2015-04-06 20:13:56 +0000
1111@@ -39,12 +39,10 @@
1112
1113 add_definitions (-DSANDBOX="${CMAKE_CURRENT_BINARY_DIR}")
1114
1115-
1116 function(add_test_by_name name)
1117 set (TEST_NAME ${name})
1118 add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled)
1119 add_test (${TEST_NAME} ${TEST_NAME})
1120- add_dependencies (${TEST_NAME} libindicatordatetimeservice)
1121 target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
1122 endfunction()
1123 add_test_by_name(test-datetime)
1124@@ -65,9 +63,41 @@
1125
1126 set (TEST_NAME manual-test-snap)
1127 add_executable (${TEST_NAME} ${TEST_NAME}.cpp)
1128-add_dependencies (${TEST_NAME} libindicatordatetimeservice)
1129 target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
1130
1131+##
1132+## EDS Tests
1133+##
1134+
1135+find_program(DBUS_RUNNER dbus-test-runner)
1136+find_program(EVOLUTION_CALENDAR_FACTORY evolution-calendar-factory PATHS /usr/lib/evolution/)
1137+find_program(EVOLUTION_SOURCE_REGISTRY evolution-source-registry PATHS /usr/lib/evolution/)
1138+find_program(GVFSD gvfsd PATHS /usr/lib/gvfs/)
1139+OPTION(EVOLUTION_SOURCE_SERVICE_NAME "DBus name for source registry")
1140+if(NOT EVOLUTION_SOURCE_SERVICE_NAME)
1141+ set(EVOLUTION_SOURCE_SERVICE_NAME "org.gnome.evolution.dataserver.Sources3")
1142+endif()
1143+
1144+function(add_eds_test_by_name name)
1145+ set (TEST_NAME ${name})
1146+ add_executable(${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled)
1147+ target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
1148+ add_test (${TEST_NAME}
1149+ ${CMAKE_CURRENT_SOURCE_DIR}/run-eds-test.sh
1150+ ${DBUS_RUNNER} # arg1: dbus-test-runner exec
1151+ ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME} # arg2: test executable path
1152+ ${TEST_NAME} # arg3: test name
1153+ ${EVOLUTION_CALENDAR_FACTORY} # arg4: evolution-calendar-factory exec
1154+ ${EVOLUTION_SOURCE_SERVICE_NAME} # arg5: dbus name for source registry
1155+ ${EVOLUTION_SOURCE_REGISTRY} # arg6: evolution-source-registry exec
1156+ ${GVFSD} # arg7: gvfsd exec
1157+ ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}-config-files) # arg8: canned config files
1158+endfunction()
1159+add_eds_test_by_name(test-eds-valarms)
1160+
1161+
1162+
1163+
1164 # disabling the timezone unit tests because they require
1165 # https://code.launchpad.net/~ted/dbus-test-runner/multi-interface-test/+merge/199724
1166 # which hasn't landed yet. These can be re-enabled as soon as that lands.
1167@@ -75,7 +105,6 @@
1168 # set (TEST_NAME ${name})
1169 # add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled)
1170 # add_test (${TEST_NAME} ${TEST_NAME})
1171-# add_dependencies (${TEST_NAME} libindicatordatetimeservice)
1172 # target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${DBUSTEST_LIBRARIES} ${GTEST_LIBS})
1173 #endfunction()
1174 #add_dbusmock_test_by_name(test-timezone-geoclue)
1175
1176=== modified file 'tests/manual-test-snap.cpp'
1177--- tests/manual-test-snap.cpp 2015-03-16 20:07:54 +0000
1178+++ tests/manual-test-snap.cpp 2015-04-06 20:13:56 +0000
1179@@ -67,18 +67,18 @@
1180 Appointment a;
1181 a.color = "green";
1182 a.summary = "Alarm";
1183- a.url = "alarm:///hello-world";
1184 a.uid = "D4B57D50247291478ED31DED17FF0A9838DED402";
1185 a.type = Appointment::UBUNTU_ALARM;
1186 a.begin = DateTime::Local(2014, 12, 25, 0, 0, 0);
1187 a.end = a.begin.end_of_day();
1188+ a.alarms.push_back(Alarm{"Alarm Text", "", a.begin});
1189
1190 auto loop = g_main_loop_new(nullptr, false);
1191- auto on_snooze = [loop](const Appointment& appt){
1192- g_message("You clicked 'Snooze' for appt url '%s'", appt.url.c_str());
1193+ auto on_snooze = [loop](const Appointment& appt, const Alarm&){
1194+ g_message("You clicked 'Snooze' for appt url '%s'", appt.summary.c_str());
1195 g_idle_add(quit_idle, loop);
1196 };
1197- auto on_ok = [loop](const Appointment&){
1198+ auto on_ok = [loop](const Appointment&, const Alarm&){
1199 g_message("You clicked 'OK'");
1200 g_idle_add(quit_idle, loop);
1201 };
1202@@ -93,7 +93,7 @@
1203
1204 auto notification_engine = std::make_shared<uin::Engine>("indicator-datetime-service");
1205 Snap snap (notification_engine, settings);
1206- snap(a, on_snooze, on_ok);
1207+ snap(a, a.alarms.front(), on_snooze, on_ok);
1208 g_main_loop_run(loop);
1209
1210 g_main_loop_unref(loop);
1211
1212=== added file 'tests/print-to.h'
1213--- tests/print-to.h 1970-01-01 00:00:00 +0000
1214+++ tests/print-to.h 2015-04-06 20:13:56 +0000
1215@@ -0,0 +1,45 @@
1216+/*
1217+ * Copyright 2015 Canonical Ltd.
1218+ *
1219+ * This program is free software: you can redistribute it and/or modify it
1220+ * under the terms of the GNU General Public License version 3, as published
1221+ * by the Free Software Foundation.
1222+ *
1223+ * This program is distributed in the hope that it will be useful, but
1224+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1225+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1226+ * PURPOSE. See the GNU General Public License for more details.
1227+ *
1228+ * You should have received a copy of the GNU General Public License along
1229+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1230+ *
1231+ * Authors:
1232+ * Charles Kerr <charles.kerr@canonical.com>
1233+ */
1234+
1235+#ifndef INDICATOR_DATETIME_TESTS_PRINT_TO
1236+#define INDICATOR_DATETIME_TESTS_PRINT_TO
1237+
1238+#include <algorithm>
1239+
1240+#include <datetime/appointment.h>
1241+
1242+namespace unity {
1243+namespace indicator {
1244+namespace datetime {
1245+
1246+/***
1247+**** PrintTo() functions for GTest to represent objects as strings
1248+***/
1249+
1250+void
1251+PrintTo(const Alarm& alarm, std::ostream* os)
1252+{
1253+ *os << "{text:'" << alarm.text << "', audio_url:'" << alarm.audio_url << "', time:'"<<alarm.time.format("%F %T")<<"'}";
1254+}
1255+
1256+} // namespace datetime
1257+} // namespace indicator
1258+} // namespace unity
1259+
1260+#endif
1261
1262=== added file 'tests/run-eds-test.sh'
1263--- tests/run-eds-test.sh 1970-01-01 00:00:00 +0000
1264+++ tests/run-eds-test.sh 2015-04-06 20:13:56 +0000
1265@@ -0,0 +1,57 @@
1266+#!/bin/sh
1267+
1268+echo ARG0=$0 # this script
1269+echo ARG1=$1 # full executable path of dbus-test-runner
1270+echo ARG2=$2 # full executable path of test app
1271+echo ARG3=$3 # test name
1272+echo ARG4=$4 # full executable path of evolution-calendar-factory
1273+echo ARG5=$5 # bus service name of calendar factory
1274+echo ARG6=$6 # full exectuable path of evolution-source-registry
1275+echo ARG7=$7 # full executable path of gvfs
1276+echo ARG8=$8 # config files
1277+
1278+# set up the tmpdir and tell the shell to purge it when we exit
1279+export TEST_TMP_DIR=$(mktemp -p "${TMPDIR:-/tmp}" -d $3-XXXXXXXXXX) || exit 1
1280+echo "running test '$3' in ${TEST_TMP_DIR}"
1281+
1282+# set up the environment variables
1283+export QT_QPA_PLATFORM=minimal
1284+export HOME=${TEST_TMP_DIR}
1285+export XDG_RUNTIME_DIR=${TEST_TMP_DIR}
1286+export XDG_CACHE_HOME=${TEST_TMP_DIR}/.cache
1287+export XDG_CONFIG_HOME=${TEST_TMP_DIR}/.config
1288+export XDG_DATA_HOME=${TEST_TMP_DIR}/.local/share
1289+export XDG_DESKTOP_DIR=${TEST_TMP_DIR}
1290+export XDG_DOCUMENTS_DIR=${TEST_TMP_DIR}
1291+export XDG_DOWNLOAD_DIR=${TEST_TMP_DIR}
1292+export XDG_MUSIC_DIR=${TEST_TMP_DIR}
1293+export XDG_PICTURES_DIR=${TEST_TMP_DIR}
1294+export XDG_PUBLICSHARE_DIR=${TEST_TMP_DIR}
1295+export XDG_TEMPLATES_DIR=${TEST_TMP_DIR}
1296+export XDG_VIDEOS_DIR=${TEST_TMP_DIR}
1297+export QORGANIZER_EDS_DEBUG=On
1298+export GIO_USE_VFS=local # needed to ensure GVFS shuts down cleanly after the test is over
1299+
1300+echo HOMEDIR=${HOME}
1301+rm -rf ${XDG_DATA_HOME}
1302+
1303+# if there are canned config files for this test, move them into place now
1304+if [ -d $8 ]; then
1305+ echo "copying files from $8 to $HOME"
1306+ cp --verbose --archive $8/. $HOME
1307+fi
1308+
1309+# run dbus-test-runner
1310+$1 --keep-env --max-wait=90 \
1311+--task $2 --task-name $3 --wait-until-complete --wait-for=org.gnome.evolution.dataserver.Calendar4 \
1312+--task $4 --task-name "evolution" --wait-until-complete -r
1313+#--task $6 --task-name "source-registry" --wait-for=org.gtk.vfs.Daemon -r \
1314+#--task $7 --task-name "gvfsd" -r
1315+rv=$?
1316+
1317+# if the test passed, blow away the tmpdir
1318+if [ $rv -eq 0 ]; then
1319+ rm -rf $TEST_TMP_DIR
1320+fi
1321+
1322+return $rv
1323
1324=== modified file 'tests/test-alarm-queue.cpp'
1325--- tests/test-alarm-queue.cpp 2015-03-16 20:07:54 +0000
1326+++ tests/test-alarm-queue.cpp 2015-04-06 20:13:56 +0000
1327@@ -48,7 +48,7 @@
1328 m_range_planner.reset(new MockRangePlanner);
1329 m_upcoming.reset(new UpcomingPlanner(m_range_planner, m_state->clock->localtime()));
1330 m_watcher.reset(new SimpleAlarmQueue(m_state->clock, m_upcoming, m_wakeup_timer));
1331- m_watcher->alarm_reached().connect([this](const Appointment& appt){
1332+ m_watcher->alarm_reached().connect([this](const Appointment& appt, const Alarm& /*alarm*/){
1333 m_triggered.push_back(appt.uid);
1334 });
1335
1336@@ -71,7 +71,7 @@
1337 const auto tomorrow_begin = now.add_days(1).start_of_day();
1338 const auto tomorrow_end = tomorrow_begin.end_of_day();
1339
1340- Appointment a1; // an alarm clock appointment
1341+ Appointment a1; // an ubuntu alarm
1342 a1.color = "red";
1343 a1.summary = "Alarm";
1344 a1.summary = "http://www.example.com/";
1345@@ -79,18 +79,20 @@
1346 a1.type = Appointment::UBUNTU_ALARM;
1347 a1.begin = tomorrow_begin;
1348 a1.end = tomorrow_end;
1349+ a1.alarms.push_back(Alarm{"Alarm Text", "", a1.begin});
1350
1351 const auto ubermorgen_begin = now.add_days(2).start_of_day();
1352 const auto ubermorgen_end = ubermorgen_begin.end_of_day();
1353
1354- Appointment a2; // a non-alarm appointment
1355+ Appointment a2; // something else
1356 a2.color = "green";
1357 a2.summary = "Other Text";
1358 a2.summary = "http://www.monkey.com/";
1359 a2.uid = "monkey";
1360- a1.type = Appointment::EVENT;
1361+ a2.type = Appointment::EVENT;
1362 a2.begin = ubermorgen_begin;
1363 a2.end = ubermorgen_end;
1364+ a2.alarms.push_back(Alarm{"Alarm Text", "", a2.begin});
1365
1366 return std::vector<Appointment>({a1, a2});
1367 }
1368@@ -105,7 +107,7 @@
1369 // Add some appointments to the planner.
1370 // One of these matches our state's localtime, so that should get triggered.
1371 std::vector<Appointment> a = build_some_appointments();
1372- a[0].begin = m_state->clock->localtime();
1373+ a[0].begin = a[0].alarms.front().time = m_state->clock->localtime();
1374 m_range_planner->appointments().set(a);
1375
1376 // Confirm that it got fired
1377@@ -135,7 +137,8 @@
1378 {
1379 const auto now = m_state->clock->localtime();
1380 std::vector<Appointment> a = build_some_appointments();
1381- a[0].begin = a[1].begin = now;
1382+ a[0].alarms.front().time = now;
1383+ a[1].alarms.front().time = now;
1384 m_range_planner->appointments().set(a);
1385
1386 ASSERT_EQ(2, m_triggered.size());
1387@@ -151,7 +154,7 @@
1388 const std::vector<Appointment> appointments = build_some_appointments();
1389 std::vector<Appointment> a;
1390 a.push_back(appointments[0]);
1391- a[0].begin = now;
1392+ a[0].alarms.front().time = now;
1393 m_range_planner->appointments().set(a);
1394 ASSERT_EQ(1, m_triggered.size());
1395 EXPECT_EQ(a[0].uid, m_triggered[0]);
1396
1397=== added directory 'tests/test-eds-valarms-config-files'
1398=== added directory 'tests/test-eds-valarms-config-files/.config'
1399=== added directory 'tests/test-eds-valarms-config-files/.config/evolution'
1400=== added directory 'tests/test-eds-valarms-config-files/.config/evolution/sources'
1401=== added file 'tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source'
1402--- tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source 1970-01-01 00:00:00 +0000
1403+++ tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source 2015-04-06 20:13:56 +0000
1404@@ -0,0 +1,21 @@
1405+
1406+[Data Source]
1407+DisplayName=Default Proxy Settings
1408+Enabled=true
1409+Parent=
1410+
1411+[Proxy]
1412+Method=default
1413+IgnoreHosts=localhost;127.0.0.0/8;::1;
1414+AutoconfigUrl=
1415+FtpHost=
1416+FtpPort=0
1417+HttpAuthPassword=
1418+HttpAuthUser=
1419+HttpHost=
1420+HttpPort=8080
1421+HttpUseAuth=false
1422+HttpsHost=
1423+HttpsPort=0
1424+SocksHost=
1425+SocksPort=0
1426
1427=== added directory 'tests/test-eds-valarms-config-files/.local'
1428=== added directory 'tests/test-eds-valarms-config-files/.local/share'
1429=== added directory 'tests/test-eds-valarms-config-files/.local/share/evolution'
1430=== added directory 'tests/test-eds-valarms-config-files/.local/share/evolution/calendar'
1431=== added directory 'tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system'
1432=== added file 'tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics'
1433--- tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics 1970-01-01 00:00:00 +0000
1434+++ tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics 2015-04-06 20:13:56 +0000
1435@@ -0,0 +1,47 @@
1436+BEGIN:VCALENDAR
1437+CALSCALE:GREGORIAN
1438+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
1439+VERSION:2.0
1440+X-EVOLUTION-DATA-REVISION:2015-04-05T21:32:47.354433Z(2)
1441+BEGIN:VEVENT
1442+UID:20150405T213247Z-4371-32011-1698-1@ubuntu-phablet
1443+DTSTAMP:20150405T213247Z
1444+DTSTART:20150424T183500Z
1445+DTEND:20150424T193554Z
1446+X-LIC-ERROR;X-LIC-ERRORTYPE=VALUE-PARSE-ERROR:Can't parse as RECUR value
1447+ in RRULE property. Removing entire property: ERROR: No Value
1448+SUMMARY:London Sprint Flight
1449+CREATED:20150405T213247Z
1450+LAST-MODIFIED:20150405T213247Z
1451+BEGIN:VALARM
1452+X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-2@ubuntu-phablet
1453+ACTION:AUDIO
1454+TRIGGER;VALUE=DURATION;RELATED=START:-P1D
1455+REPEAT:3
1456+DURATION:PT2M
1457+END:VALARM
1458+BEGIN:VALARM
1459+X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-3@ubuntu-phablet
1460+ACTION:DISPLAY
1461+DESCRIPTION:Time to pack!
1462+TRIGGER;VALUE=DURATION;RELATED=START:-P1D
1463+REPEAT:3
1464+DURATION:PT2M
1465+END:VALARM
1466+BEGIN:VALARM
1467+X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-5@ubuntu-phablet
1468+ACTION:AUDIO
1469+TRIGGER;VALUE=DURATION;RELATED=START:-PT3H
1470+REPEAT:3
1471+DURATION:PT2M
1472+END:VALARM
1473+BEGIN:VALARM
1474+X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-6@ubuntu-phablet
1475+ACTION:DISPLAY
1476+DESCRIPTION:Go to the airport!
1477+TRIGGER;VALUE=DURATION;RELATED=START:-PT3H
1478+REPEAT:3
1479+DURATION:PT2M
1480+END:VALARM
1481+END:VEVENT
1482+END:VCALENDAR
1483
1484=== added file 'tests/test-eds-valarms.cpp'
1485--- tests/test-eds-valarms.cpp 1970-01-01 00:00:00 +0000
1486+++ tests/test-eds-valarms.cpp 2015-04-06 20:13:56 +0000
1487@@ -0,0 +1,101 @@
1488+/*
1489+ * Copyright 2015 Canonical Ltd.
1490+ *
1491+ * This program is free software: you can redistribute it and/or modify it
1492+ * under the terms of the GNU General Public License version 3, as published
1493+ * by the Free Software Foundation.
1494+ *
1495+ * This program is distributed in the hope that it will be useful, but
1496+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1497+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1498+ * PURPOSE. See the GNU General Public License for more details.
1499+ *
1500+ * You should have received a copy of the GNU General Public License along
1501+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1502+ *
1503+ * Authors:
1504+ * Charles Kerr <charles.kerr@canonical.com>
1505+ */
1506+
1507+#include <algorithm>
1508+
1509+#include <datetime/alarm-queue-simple.h>
1510+#include <datetime/clock-mock.h>
1511+#include <datetime/engine-eds.h>
1512+#include <datetime/planner-range.h>
1513+
1514+#include <gtest/gtest.h>
1515+
1516+#include "glib-fixture.h"
1517+#include "print-to.h"
1518+#include "timezone-mock.h"
1519+#include "wakeup-timer-mock.h"
1520+
1521+using namespace unity::indicator::datetime;
1522+using VAlarmFixture = GlibFixture;
1523+
1524+/***
1525+****
1526+***/
1527+
1528+TEST_F(VAlarmFixture, MultipleAppointments)
1529+{
1530+ // start the EDS engine
1531+ auto engine = std::make_shared<EdsEngine>();
1532+
1533+ // we need a consistent timezone for the planner and our local DateTimes
1534+ constexpr char const * zone_str {"America/Chicago"};
1535+ auto tz = std::make_shared<MockTimezone>(zone_str);
1536+ auto gtz = g_time_zone_new(zone_str);
1537+
1538+ // make a planner that looks at the first half of 2015 in EDS
1539+ auto planner = std::make_shared<SimpleRangePlanner>(engine, tz);
1540+ const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0};
1541+ const DateTime range_end {gtz, 2015,6,31,23,59,59.5};
1542+ planner->range().set(std::make_pair(range_begin, range_end));
1543+
1544+ // give EDS a moment to load
1545+ if (planner->appointments().get().empty()) {
1546+ g_message("waiting a moment for EDS to load...");
1547+ auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){
1548+ g_message("ah, they loaded");
1549+ if (!appointments.empty())
1550+ g_main_loop_quit(loop);
1551+ };
1552+ core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed));
1553+ constexpr int max_wait_sec = 10;
1554+ wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND);
1555+ }
1556+
1557+ // the planner should match what we've got in the calendar.ics file
1558+ const auto appts = planner->appointments().get();
1559+ ASSERT_EQ(1, appts.size());
1560+ const auto& appt = appts.front();
1561+ ASSERT_EQ(8, appt.alarms.size());
1562+ EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,35,0)}), appt.alarms[0]);
1563+ EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,37,0)}), appt.alarms[1]);
1564+ EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,39,0)}), appt.alarms[2]);
1565+ EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,41,0)}), appt.alarms[3]);
1566+ EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,35,0)}), appt.alarms[4]);
1567+ EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,37,0)}), appt.alarms[5]);
1568+ EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,39,0)}), appt.alarms[6]);
1569+ EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,41,0)}), appt.alarms[7]);
1570+
1571+ // now let's try this out with AlarmQueue...
1572+ // hook the planner up to a SimpleAlarmQueue and confirm that it triggers for each of the reminders
1573+ auto mock_clock = std::make_shared<MockClock>(range_begin);
1574+ std::shared_ptr<Clock> clock = mock_clock;
1575+ std::shared_ptr<WakeupTimer> wakeup_timer = std::make_shared<MockWakeupTimer>(clock);
1576+ auto alarm_queue = std::make_shared<SimpleAlarmQueue>(clock, planner, wakeup_timer);
1577+ int triggered_count = 0;
1578+ alarm_queue->alarm_reached().connect([&triggered_count, appt](const Appointment&, const Alarm& active_alarm) {
1579+ EXPECT_TRUE(std::find(appt.alarms.begin(), appt.alarms.end(), active_alarm) != appt.alarms.end());
1580+ ++triggered_count;
1581+ });
1582+ for (auto now=range_begin; now<range_end; now+=std::chrono::minutes{1})
1583+ mock_clock->set_localtime(now);
1584+ EXPECT_EQ(appt.alarms.size(), triggered_count);
1585+
1586+ // cleanup
1587+ g_time_zone_unref(gtz);
1588+}
1589
1590=== modified file 'tests/test-live-actions.cpp'
1591--- tests/test-live-actions.cpp 2015-03-16 20:07:54 +0000
1592+++ tests/test-live-actions.cpp 2015-04-06 20:13:56 +0000
1593@@ -319,10 +319,6 @@
1594 a.type = Appointment::UBUNTU_ALARM;
1595 m_actions->phone_open_appointment(a);
1596 EXPECT_EQ(clock_app_url, m_live_actions->last_url);
1597-
1598- a.url = "appid://blah";
1599- m_actions->phone_open_appointment(a);
1600- EXPECT_EQ(a.url, m_live_actions->last_url);
1601 }
1602
1603 TEST_F(LiveActionsFixture, PhoneOpenCalendarApp)
1604@@ -392,7 +388,6 @@
1605 Appointment a1;
1606 a1.color = "green";
1607 a1.summary = "write unit tests";
1608- a1.url = "http://www.ubuntu.com/";
1609 a1.uid = "D4B57D50247291478ED31DED17FF0A9838DED402";
1610 a1.begin = tomorrow_begin;
1611 a1.end = tomorrow_end;
1612@@ -403,7 +398,6 @@
1613 Appointment a2;
1614 a2.color = "orange";
1615 a2.summary = "code review";
1616- a2.url = "http://www.ubuntu.com/";
1617 a2.uid = "2756ff7de3745bbffd65d2e4779c37c7ca60d843";
1618 a2.begin = ubermorgen_begin;
1619 a2.end = ubermorgen_end;
1620
1621=== modified file 'tests/test-snap.cpp'
1622--- tests/test-snap.cpp 2015-03-16 20:07:54 +0000
1623+++ tests/test-snap.cpp 2015-04-06 20:13:56 +0000
1624@@ -106,12 +106,12 @@
1625 // init the Appointment
1626 appt.color = "green";
1627 appt.summary = "Alarm";
1628- appt.url = "alarm:///hello-world";
1629 appt.uid = "D4B57D50247291478ED31DED17FF0A9838DED402";
1630 appt.type = Appointment::EVENT;
1631 const auto christmas = DateTime::Local(2015,12,25,0,0,0);
1632 appt.begin = christmas.start_of_day();
1633 appt.end = christmas.end_of_day();
1634+ appt.alarms.push_back(Alarm{"Alarm Text", "", appt.begin});
1635
1636 service = dbus_test_service_new(nullptr);
1637
1638@@ -343,8 +343,8 @@
1639 make_interactive();
1640
1641 // call the Snap Decision
1642- auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);};
1643- snap(appt, func, func);
1644+ auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);};
1645+ snap(appt, appt.alarms.front(), func, func);
1646
1647 // confirm that Notify got called once
1648 guint len = 0;
1649@@ -393,8 +393,8 @@
1650 make_interactive();
1651
1652 // invoke the notification
1653- auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);};
1654- (*snap)(appt, func, func);
1655+ auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);};
1656+ (*snap)(appt, appt.alarms.front(), func, func);
1657
1658 wait_msec(1000);
1659
1660@@ -448,8 +448,8 @@
1661 make_interactive();
1662
1663 // invoke the notification
1664- auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);};
1665- (*snap)(appt, func, func);
1666+ auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);};
1667+ (*snap)(appt, appt.alarms.front(), func, func);
1668
1669 wait_msec(1000);
1670
1671@@ -484,14 +484,14 @@
1672 {
1673 auto settings = std::make_shared<Settings>();
1674 auto ne = std::make_shared<unity::indicator::notifications::Engine>(APP_NAME);
1675- auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);};
1676+ auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);};
1677 GError * error = nullptr;
1678
1679 // invoke a snap decision while haptic feedback is set to "pulse",
1680 // confirm that VibratePattern got called
1681 settings->alarm_haptic.set("pulse");
1682 auto snap = new Snap (ne, settings);
1683- (*snap)(appt, func, func);
1684+ (*snap)(appt, appt.alarms.front(), func, func);
1685 wait_msec(100);
1686 EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (haptic_mock,
1687 haptic_obj,
1688@@ -506,7 +506,7 @@
1689 dbus_test_dbus_mock_object_clear_method_calls (haptic_mock, haptic_obj, &error);
1690 settings->alarm_haptic.set("none");
1691 snap = new Snap (ne, settings);
1692- (*snap)(appt, func, func);
1693+ (*snap)(appt, appt.alarms.front(), func, func);
1694 wait_msec(100);
1695 EXPECT_FALSE (dbus_test_dbus_mock_object_check_method_call (haptic_mock,
1696 haptic_obj,
1697
1698=== modified file 'tests/timezone-mock.h'
1699--- tests/timezone-mock.h 2014-03-09 17:48:18 +0000
1700+++ tests/timezone-mock.h 2015-04-06 20:13:56 +0000
1701@@ -30,6 +30,7 @@
1702 {
1703 public:
1704 MockTimezone() =default;
1705+ explicit MockTimezone(const std::string& zone) {timezone.set(zone);}
1706 ~MockTimezone() =default;
1707 };
1708
1709
1710=== added file 'tests/wakeup-timer-mock.h'
1711--- tests/wakeup-timer-mock.h 1970-01-01 00:00:00 +0000
1712+++ tests/wakeup-timer-mock.h 2015-04-06 20:13:56 +0000
1713@@ -0,0 +1,78 @@
1714+/*
1715+ * Copyright 2015 Canonical Ltd.
1716+ *
1717+ * This program is free software: you can redistribute it and/or modify it
1718+ * under the terms of the GNU General Public License version 3, as published
1719+ * by the Free Software Foundation.
1720+ *
1721+ * This program is distributed in the hope that it will be useful, but
1722+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1723+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1724+ * PURPOSE. See the GNU General Public License for more details.
1725+ *
1726+ * You should have received a copy of the GNU General Public License along
1727+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1728+ *
1729+ * Authors:
1730+ * Charles Kerr <charles.kerr@canonical.com>
1731+ */
1732+
1733+#ifndef INDICATOR_DATETIME_WAKEUP_TIMER_MOCK_H
1734+#define INDICATOR_DATETIME_WAKEUP_TIMER_MOCK_H
1735+
1736+#include <datetime/clock.h>
1737+#include <datetime/wakeup-timer.h>
1738+
1739+namespace unity {
1740+namespace indicator {
1741+namespace datetime {
1742+
1743+/***
1744+****
1745+***/
1746+
1747+/**
1748+ * \brief A one-shot timer that emits a signal when its timeout is reached.
1749+ */
1750+class MockWakeupTimer: public WakeupTimer
1751+{
1752+public:
1753+ explicit MockWakeupTimer(const std::shared_ptr<Clock>& clock):
1754+ m_clock(clock)
1755+ {
1756+ m_clock->minute_changed.connect([this](){
1757+ test_for_wakeup();
1758+ });
1759+ }
1760+
1761+ virtual ~MockWakeupTimer() =default;
1762+
1763+ virtual void set_wakeup_time (const DateTime& wakeup_time) override {
1764+ m_wakeup_time = wakeup_time;
1765+ test_for_wakeup();
1766+ }
1767+
1768+ virtual core::Signal<>& timeout() override { return m_timeout; }
1769+
1770+private:
1771+
1772+ void test_for_wakeup()
1773+ {
1774+ if (DateTime::is_same_minute(m_clock->localtime(), m_wakeup_time))
1775+ m_timeout();
1776+ }
1777+
1778+ core::Signal<> m_timeout;
1779+ const std::shared_ptr<Clock>& m_clock;
1780+ DateTime m_wakeup_time;
1781+};
1782+
1783+/***
1784+****
1785+***/
1786+
1787+} // namespace datetime
1788+} // namespace indicator
1789+} // namespace unity
1790+
1791+#endif // INDICATOR_DATETIME_WAKEUP_TIMER_MOCK_H

Subscribers

People subscribed via source and target branches