Merge lp:~charlesk/indicator-datetime/lp-1456281-fix-15.04-alarm-regression into lp:indicator-datetime/15.04

Proposed by Charles Kerr on 2015-05-21
Status: Merged
Approved by: Ted Gould on 2015-05-21
Approved revision: 428
Merged at revision: 414
Proposed branch: lp:~charlesk/indicator-datetime/lp-1456281-fix-15.04-alarm-regression
Merge into: lp:indicator-datetime/15.04
Diff against target: 1442 lines (+809/-433)
17 files modified
src/engine-eds.cpp (+348/-200)
tests/CMakeLists.txt (+15/-14)
tests/print-to.h (+10/-1)
tests/run-eds-ics-test.sh (+10/-0)
tests/test-eds-ics-all-day-events.cpp (+91/-0)
tests/test-eds-ics-all-day-events.ics (+19/-0)
tests/test-eds-ics-config-files/.config/evolution/sources/system-proxy.source (+21/-0)
tests/test-eds-ics-nonrepeating-events.cpp (+93/-0)
tests/test-eds-ics-nonrepeating-events.ics (+27/-0)
tests/test-eds-ics-repeating-events.cpp (+100/-0)
tests/test-eds-ics-repeating-events.ics (+28/-0)
tests/test-eds-ics-repeating-valarms.ics (+47/-0)
tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source (+0/-21)
tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics (+0/-28)
tests/test-eds-tasks.cpp (+0/-101)
tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source (+0/-21)
tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics (+0/-47)
To merge this branch: bzr merge lp:~charlesk/indicator-datetime/lp-1456281-fix-15.04-alarm-regression
Reviewer Review Type Date Requested Status
Ted Gould (community) 2015-05-21 Approve on 2015-05-21
PS Jenkins bot (community) continuous-integration Approve on 2015-05-21
Review via email: mp+259713@code.launchpad.net

Commit message

Fix regression that caused nonrepeating alarms to go off at the wrong time.

Description of the change

== Description of the Change

So, for the purposes of this description there are two kinds of ECalComponents: the primary one for an event, and instance components that are what you get when you have a repeating event.

Timezones for "floating" times (e.g., an alarm that goes off at 6:30 AM regardless of the current timezone) get distorted when you call e_cal_util_generate_alarms_for_comp() on an instance component. This caused nonrepeating alarms to go off at the wrong time. We need to build the Alarms from primary components.

So, the old code flow was

    for (instance : e_cal_client_generate_instances(client))
        for (alarm : e_cal_util_generate_alarms_for_comp(instance))

The new flow is:

    for (component : e_cal_client_get_object_list_as_comps(client))
        for (alarm : e_cal_util_generate_alarms_for_list(component))

...if only the actual code were that easy :-)

Also, ensure EDS regression tests exist for both repeating and nonrepeating alarms.

== Checklist

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

No

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

In sync with indicator-datetime/15.04

> 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?

krillin ubuntu-touch/rc-proposed/bq-aquaris.en r11

> What manual tests are relevant for this MP?

indicator-datetime/disable-one-time-alarms-after-notification

> 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.
424. By Charles Kerr on 2015-05-21

remove a little more leftover code from the false starts

Ted Gould (ted) wrote :

There seems to be a small object leak, but otherwise I can only find faults in the EDS API :-)

review: Needs Fixing
425. By Charles Kerr on 2015-05-21

in engine-eds.cpp's get_appointments(), clear the begin_str and end_str variables as soon as we're done with them.

426. By Charles Kerr on 2015-05-21

in engine-eds.cpp's on_object_list_ready(), always mark the subtask as finished even if the dbus call failed.

427. By Charles Kerr on 2015-05-21

in run-eds-ics-test.sh, slightly cleaner ics file copying

428. By Charles Kerr on 2015-05-21

in eds-engine, add a occur-in-time-range sexp to handle events that are interesting but don't require user notification alarms

Ted Gould (ted) wrote :

Looks like good updates. I really like moving the deleter function into the class, makes it way more readable and easier to track the memory.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/engine-eds.cpp'
2--- src/engine-eds.cpp 2015-05-08 00:10:37 +0000
3+++ src/engine-eds.cpp 2015-05-21 12:47:48 +0000
4@@ -79,9 +79,6 @@
5 const Timezone& timezone,
6 std::function<void(const std::vector<Appointment>&)> func)
7 {
8- const auto begin_timet = begin.to_unix();
9- const auto end_timet = end.to_unix();
10-
11 const auto b_str = begin.format("%F %T");
12 const auto e_str = end.format("%F %T");
13 g_debug("getting all appointments from [%s ... %s]", b_str.c_str(), e_str.c_str());
14@@ -106,42 +103,51 @@
15 *** walk through the sources to build the appointment list
16 **/
17
18- auto task_deleter = [](Task* task){
19- // give the caller the (sorted) finished product
20- auto& a = task->appointments;
21- std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;});
22- task->func(a);
23- // we're done; delete the task
24- g_debug("time to delete task %p", (void*)task);
25- delete task;
26- };
27-
28- std::shared_ptr<Task> main_task(new Task(this, func), task_deleter);
29+ auto gtz = default_timezone != nullptr
30+ ? g_time_zone_new(icaltimezone_get_location(default_timezone))
31+ : g_time_zone_new_local();
32+ auto main_task = std::make_shared<Task>(this, func, default_timezone, gtz, begin, end);
33
34 for (auto& kv : m_clients)
35 {
36 auto& client = kv.second;
37 if (default_timezone != nullptr)
38 e_cal_client_set_default_timezone(client, default_timezone);
39+ g_debug("calling e_cal_client_generate_instances for %p", (void*)client);
40
41- // start a new subtask to enumerate all the components in this client.
42 auto& source = kv.first;
43 auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR);
44 const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension));
45- g_debug("calling e_cal_client_generate_instances for %p", (void*)client);
46- auto subtask = new AppointmentSubtask(main_task,
47- client,
48- color,
49- default_timezone,
50- begin_timet,
51- end_timet);
52- e_cal_client_generate_instances(client,
53- begin_timet,
54- end_timet,
55- m_cancellable,
56- my_get_appointments_foreach,
57- subtask,
58- [](gpointer g){delete static_cast<AppointmentSubtask*>(g);});
59+
60+ auto begin_str = isodate_from_time_t(begin.to_unix());
61+ auto end_str = isodate_from_time_t(end.to_unix());
62+ auto sexp_fmt = g_strdup_printf("(%%s? (make-time \"%s\") (make-time \"%s\"))", begin_str, end_str);
63+ g_clear_pointer(&begin_str, g_free);
64+ g_clear_pointer(&end_str, g_free);
65+
66+ // ask EDS about alarms that occur in this window...
67+ auto sexp = g_strdup_printf(sexp_fmt, "has-alarms-in-range");
68+ g_debug("%s alarm sexp is %s", G_STRLOC, sexp);
69+ e_cal_client_get_object_list_as_comps(
70+ client,
71+ sexp,
72+ m_cancellable,
73+ on_alarm_component_list_ready,
74+ new ClientSubtask(main_task, client, color));
75+ g_clear_pointer(&sexp, g_free);
76+
77+ // ask EDS about events that occur in this window...
78+ sexp = g_strdup_printf(sexp_fmt, "occur-in-time-range");
79+ g_debug("%s event sexp is %s", G_STRLOC, sexp);
80+ e_cal_client_get_object_list_as_comps(
81+ client,
82+ sexp,
83+ m_cancellable,
84+ on_event_component_list_ready,
85+ new ClientSubtask(main_task, client, color));
86+ g_clear_pointer(&sexp, g_free);
87+
88+ g_clear_pointer(&sexp_fmt, g_free);
89 }
90 }
91
92@@ -409,30 +415,45 @@
93 {
94 Impl* p;
95 appointment_func func;
96+ icaltimezone* default_timezone; // pointer owned by libical
97+ GTimeZone* gtz;
98 std::vector<Appointment> appointments;
99- Task(Impl* p_in, const appointment_func& func_in): p(p_in), func(func_in) {}
100+ const DateTime begin;
101+ const DateTime end;
102+
103+ Task(Impl* p_in,
104+ appointment_func func_in,
105+ icaltimezone* tz_in,
106+ GTimeZone* gtz_in,
107+ const DateTime& begin_in,
108+ const DateTime& end_in):
109+ p{p_in},
110+ func{func_in},
111+ default_timezone{tz_in},
112+ gtz{gtz_in},
113+ begin{begin_in},
114+ end{end_in} {}
115+
116+ ~Task() {
117+ g_clear_pointer(&gtz, g_time_zone_unref);
118+ // give the caller the sorted finished product
119+ auto& a = appointments;
120+ std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;});
121+ func(a);
122+ };
123 };
124
125- struct AppointmentSubtask
126+ struct ClientSubtask
127 {
128 std::shared_ptr<Task> task;
129 ECalClient* client;
130 std::string color;
131- icaltimezone* default_timezone;
132- time_t begin;
133- time_t end;
134
135- AppointmentSubtask(const std::shared_ptr<Task>& task_in,
136- ECalClient* client_in,
137- const char* color_in,
138- icaltimezone* default_tz,
139- time_t begin_,
140- time_t end_):
141+ ClientSubtask(const std::shared_ptr<Task>& task_in,
142+ ECalClient* client_in,
143+ const char* color_in):
144 task(task_in),
145- client(client_in),
146- default_timezone(default_tz),
147- begin(begin_),
148- end(end_)
149+ client(client_in)
150 {
151 if (color_in)
152 color = color_in;
153@@ -482,163 +503,290 @@
154 return ret;
155 }
156
157- static gboolean
158- my_get_appointments_foreach(ECalComponent* component,
159- time_t begin,
160- time_t end,
161- gpointer gsubtask)
162- {
163+ static void
164+ on_alarm_component_list_ready(GObject * oclient,
165+ GAsyncResult * res,
166+ gpointer gsubtask)
167+ {
168+ GError * error = NULL;
169+ GSList * comps_slist = NULL;
170+ auto subtask = static_cast<ClientSubtask*>(gsubtask);
171+
172+ if (e_cal_client_get_object_list_as_comps_finish(E_CAL_CLIENT(oclient),
173+ res,
174+ &comps_slist,
175+ &error))
176+ {
177+ // _generate_alarms takes a GList, so make a shallow one
178+ GList * comps_list = nullptr;
179+ for (auto l=comps_slist; l!=nullptr; l=l->next)
180+ comps_list = g_list_prepend(comps_list, l->data);
181+
182+ constexpr std::array<ECalComponentAlarmAction,1> omit = {
183+ (ECalComponentAlarmAction)-1
184+ }; // list of action types to omit, terminated with -1
185+ GSList * comp_alarms = nullptr;
186+ e_cal_util_generate_alarms_for_list(
187+ comps_list,
188+ subtask->task->begin.to_unix(),
189+ subtask->task->end.to_unix(),
190+ const_cast<ECalComponentAlarmAction*>(omit.data()),
191+ &comp_alarms,
192+ e_cal_client_resolve_tzid_cb,
193+ oclient,
194+ subtask->task->default_timezone);
195+
196+ // walk the alarms & add them
197+ for (auto l=comp_alarms; l!=nullptr; l=l->next)
198+ add_alarms_to_subtask(static_cast<ECalComponentAlarms*>(l->data), subtask, subtask->task->gtz);
199+
200+ // cleanup
201+ e_cal_free_alarms(comp_alarms);
202+ g_list_free(comps_list);
203+ e_cal_client_free_ecalcomp_slist(comps_slist);
204+ }
205+ else if (error != nullptr)
206+ {
207+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
208+ g_warning("can't get ecalcomponent list: %s", error->message);
209+
210+ g_error_free(error);
211+ }
212+
213+ delete subtask;
214+ }
215+
216+ static void
217+ on_event_component_list_ready(GObject * oclient,
218+ GAsyncResult * res,
219+ gpointer gsubtask)
220+ {
221+ GError * error = NULL;
222+ GSList * comps_slist = NULL;
223+ auto subtask = static_cast<ClientSubtask*>(gsubtask);
224+
225+ if (e_cal_client_get_object_list_as_comps_finish(E_CAL_CLIENT(oclient),
226+ res,
227+ &comps_slist,
228+ &error))
229+ {
230+ for (auto l=comps_slist; l!=nullptr; l=l->next)
231+ add_event_to_subtask(static_cast<ECalComponent*>(l->data), subtask, subtask->task->gtz);
232+
233+ e_cal_client_free_ecalcomp_slist(comps_slist);
234+ }
235+ else if (error != nullptr)
236+ {
237+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
238+ g_warning("can't get ecalcomponent list: %s", error->message);
239+
240+ g_error_free(error);
241+ }
242+
243+ delete subtask;
244+ }
245+
246+ static DateTime
247+ datetime_from_component_date_time(const ECalComponentDateTime & in,
248+ GTimeZone * default_timezone)
249+ {
250+ DateTime out;
251+
252+ g_return_val_if_fail(in.value != nullptr, out);
253+
254+ auto gtz = in.tzid == nullptr ? g_time_zone_ref(default_timezone)
255+ : g_time_zone_new(in.tzid);
256+ out = DateTime(gtz,
257+ in.value->year,
258+ in.value->month,
259+ in.value->day,
260+ in.value->hour,
261+ in.value->minute,
262+ in.value->second);
263+ g_time_zone_unref(gtz);
264+ return out;
265+ }
266+
267+ static bool
268+ is_component_interesting(ECalComponent * component)
269+ {
270+ // we only want calendar events and vtodos
271 const auto vtype = e_cal_component_get_vtype(component);
272- auto subtask = static_cast<AppointmentSubtask*>(gsubtask);
273-
274- if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO))
275- {
276- const gchar* uid = nullptr;
277- e_cal_component_get_uid(component, &uid);
278-
279- auto status = ICAL_STATUS_NONE;
280- e_cal_component_get_status(component, &status);
281-
282- // get the timezone we want to use for generated Appointments/Alarms
283- const char * location = icaltimezone_get_location(subtask->default_timezone);
284- auto gtz = g_time_zone_new(location);
285- g_debug("timezone abbreviation is %s", g_time_zone_get_abbreviation (gtz, 0));
286-
287- const DateTime begin_dt { gtz, begin };
288- const DateTime end_dt { gtz, end };
289- g_debug ("got appointment from %s to %s, uid %s status %d",
290- begin_dt.format("%F %T %z").c_str(),
291- end_dt.format("%F %T %z").c_str(),
292- uid,
293- (int)status);
294-
295- // look for the in-house tags
296- bool disabled = false;
297- Appointment::Type type = Appointment::EVENT;
298- GSList * categ_list = nullptr;
299- e_cal_component_get_categories_list (component, &categ_list);
300- for (GSList * l=categ_list; l!=nullptr; l=l->next) {
301- auto tag = static_cast<const char*>(l->data);
302- if (!g_strcmp0(tag, TAG_ALARM))
303- type = Appointment::UBUNTU_ALARM;
304- if (!g_strcmp0(tag, TAG_DISABLED))
305- disabled = true;
306- }
307- e_cal_component_free_categories_list(categ_list);
308-
309- if ((uid != nullptr) &&
310- (!disabled) &&
311- (status != ICAL_STATUS_COMPLETED) &&
312- (status != ICAL_STATUS_CANCELLED))
313- {
314- constexpr std::array<ECalComponentAlarmAction,1> omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1
315- Appointment appointment;
316-
317- ECalComponentText text {};
318- e_cal_component_get_summary(component, &text);
319- if (text.value)
320- appointment.summary = text.value;
321-
322- auto icc = e_cal_component_get_icalcomponent(component); // component owns icc
323- if (icc)
324- {
325- g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak
326-
327- auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY);
328- while (icalprop)
329- {
330- const char * x_name = icalproperty_get_x_name(icalprop);
331- if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL))
332- {
333- const char * url = icalproperty_get_value_as_string(icalprop);
334- if ((url != nullptr) && appointment.activation_url.empty())
335- appointment.activation_url = url;
336- }
337-
338- icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY);
339- }
340- }
341-
342- appointment.begin = begin_dt;
343- appointment.end = end_dt;
344- appointment.color = subtask->color;
345- appointment.uid = uid;
346- appointment.type = type;
347-
348- auto e_alarms = e_cal_util_generate_alarms_for_comp(component,
349- subtask->begin,
350- subtask->end,
351- const_cast<ECalComponentAlarmAction*>(omit.data()),
352- e_cal_client_resolve_tzid_cb,
353- subtask->client,
354- nullptr);
355-
356- std::map<DateTime,Alarm> alarms;
357-
358- if (e_alarms != nullptr)
359- {
360- for (auto l=e_alarms->alarms; l!=nullptr; l=l->next)
361- {
362- auto ai = static_cast<ECalComponentAlarmInstance*>(l->data);
363- auto a = e_cal_component_get_alarm(component, ai->auid);
364-
365- if (a != nullptr)
366- {
367- const DateTime alarm_begin{gtz, ai->trigger};
368- auto& alarm = alarms[alarm_begin];
369-
370- if (alarm.text.empty())
371- alarm.text = get_alarm_text(a);
372- if (alarm.audio_url.empty())
373- alarm.audio_url = get_alarm_sound_url(a);
374- if (!alarm.time.is_set())
375- alarm.time = alarm_begin;
376-
377- e_cal_component_alarm_free(a);
378- }
379- }
380-
381- e_cal_component_alarms_free(e_alarms);
382- }
383- // Hm, no alarm triggers?
384- // That's a bug in alarms created by some versions of ubuntu-ui-toolkit.
385- // If that's what's happening here, let's handle those alarms anyway
386- // by effectively injecting a TRIGGER;VALUE=DURATION;RELATED=START:PT0S
387- else if (appointment.is_ubuntu_alarm())
388- {
389- Alarm tmp;
390- tmp.time = appointment.begin;
391-
392- auto auids = e_cal_component_get_alarm_uids(component);
393- for(auto l=auids; l!=nullptr; l=l->next)
394- {
395- const auto auid = static_cast<const char*>(l->data);
396- auto a = e_cal_component_get_alarm(component, auid);
397- if (a != nullptr)
398- {
399- if (tmp.text.empty())
400- tmp.text = get_alarm_text(a);
401- if (tmp.audio_url.empty())
402- tmp.audio_url = get_alarm_sound_url(a);
403- e_cal_component_alarm_free(a);
404- }
405- }
406- cal_obj_uid_list_free(auids);
407-
408- alarms[tmp.time] = tmp;
409- }
410-
411- appointment.alarms.reserve(alarms.size());
412- for (const auto& it : alarms)
413- appointment.alarms.push_back(it.second);
414-
415- subtask->task->appointments.push_back(appointment);
416- }
417-
418- g_time_zone_unref(gtz);
419- }
420-
421- return G_SOURCE_CONTINUE;
422+ if ((vtype != E_CAL_COMPONENT_EVENT) &&
423+ (vtype != E_CAL_COMPONENT_TODO))
424+ return false;
425+
426+ // we're not interested in completed or cancelled components
427+ auto status = ICAL_STATUS_NONE;
428+ e_cal_component_get_status(component, &status);
429+ if ((status == ICAL_STATUS_COMPLETED) ||
430+ (status == ICAL_STATUS_CANCELLED))
431+ return false;
432+
433+ // we don't want disabled alarms
434+ bool disabled = false;
435+ GSList * categ_list = nullptr;
436+ e_cal_component_get_categories_list (component, &categ_list);
437+ for (GSList * l=categ_list; l!=nullptr; l=l->next) {
438+ auto tag = static_cast<const char*>(l->data);
439+ if (!g_strcmp0(tag, TAG_DISABLED))
440+ disabled = true;
441+ }
442+ e_cal_component_free_categories_list(categ_list);
443+ if (disabled)
444+ return false;
445+
446+ return true;
447+ }
448+
449+ static Appointment
450+ get_appointment(ECalComponent * component, GTimeZone * gtz)
451+ {
452+ Appointment baseline;
453+
454+ // get appointment.uid
455+ const gchar* uid = nullptr;
456+ e_cal_component_get_uid(component, &uid);
457+ if (uid != nullptr)
458+ baseline.uid = uid;
459+
460+ // get appointment.summary
461+ ECalComponentText text {};
462+ e_cal_component_get_summary(component, &text);
463+ if (text.value)
464+ baseline.summary = text.value;
465+
466+ // get appointment.begin
467+ ECalComponentDateTime eccdt_tmp {};
468+ e_cal_component_get_dtstart(component, &eccdt_tmp);
469+ baseline.begin = datetime_from_component_date_time(eccdt_tmp, gtz);
470+ e_cal_component_free_datetime(&eccdt_tmp);
471+
472+ // get appointment.end
473+ e_cal_component_get_dtend(component, &eccdt_tmp);
474+ baseline.end = eccdt_tmp.value != nullptr
475+ ? datetime_from_component_date_time(eccdt_tmp, gtz)
476+ : baseline.begin;
477+ e_cal_component_free_datetime(&eccdt_tmp);
478+
479+ // get appointment.activation_url from x-props
480+ auto icc = e_cal_component_get_icalcomponent(component); // icc owned by component
481+ auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY);
482+ while (icalprop != nullptr) {
483+ const char * x_name = icalproperty_get_x_name(icalprop);
484+ if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL)) {
485+ const char * url = icalproperty_get_value_as_string(icalprop);
486+ if ((url != nullptr) && baseline.activation_url.empty())
487+ baseline.activation_url = url;
488+ }
489+ icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY);
490+ }
491+
492+ // get appointment.type
493+ baseline.type = Appointment::EVENT;
494+ GSList * categ_list = nullptr;
495+ e_cal_component_get_categories_list (component, &categ_list);
496+ for (GSList * l=categ_list; l!=nullptr; l=l->next) {
497+ auto tag = static_cast<const char*>(l->data);
498+ if (!g_strcmp0(tag, TAG_ALARM))
499+ baseline.type = Appointment::UBUNTU_ALARM;
500+ }
501+ e_cal_component_free_categories_list(categ_list);
502+
503+ g_debug("%s got appointment from %s to %s: %s", G_STRLOC,
504+ baseline.begin.format("%F %T %z").c_str(),
505+ baseline.end.format("%F %T %z").c_str(),
506+ icalcomponent_as_ical_string(icc) /* string owned by ical */);
507+
508+ return baseline;
509+ }
510+
511+ static void
512+ add_event_to_subtask(ECalComponent * component,
513+ ClientSubtask * subtask,
514+ GTimeZone * gtz)
515+ {
516+ // events with alarms are covered by add_alarm_to_subtask(),
517+ // so skip them here
518+ auto auids = e_cal_component_get_alarm_uids(component);
519+ const bool has_alarms = auids != nullptr;
520+ cal_obj_uid_list_free(auids);
521+ if (has_alarms)
522+ return;
523+
524+ // add it. simple, eh?
525+ if (is_component_interesting(component))
526+ {
527+ Appointment appointment = get_appointment(component, gtz);
528+ appointment.color = subtask->color;
529+ subtask->task->appointments.push_back(appointment);
530+ }
531+ }
532+
533+ static void
534+ add_alarms_to_subtask(ECalComponentAlarms * comp_alarms,
535+ ClientSubtask * subtask,
536+ GTimeZone * gtz)
537+ {
538+ auto& component = comp_alarms->comp;
539+
540+ if (!is_component_interesting(component))
541+ return;
542+
543+ Appointment baseline = get_appointment(component, gtz);
544+ baseline.color = subtask->color;
545+
546+ /**
547+ *** Now loop through comp_alarms to get information that we need
548+ *** to build the instance appointments and their alarms.
549+ ***
550+ *** Outer map key is the instance component's start + end time.
551+ *** We build Appointment.begin and .end from that.
552+ ***
553+ *** inner map key is the alarm trigger, we build Alarm.time from that.
554+ ***
555+ *** inner map value is the Alarm.
556+ ***
557+ *** We map the alarms based on their trigger time so that we
558+ *** can fold together multiple valarms that trigger for the
559+ *** same componeng at the same time. This is commonplace;
560+ *** e.g. one valarm will have a display action and another
561+ *** will specify a sound to be played.
562+ */
563+ std::map<std::pair<DateTime,DateTime>,std::map<DateTime,Alarm>> alarms;
564+ for (auto l=comp_alarms->alarms; l!=nullptr; l=l->next)
565+ {
566+ auto ai = static_cast<ECalComponentAlarmInstance*>(l->data);
567+ auto a = e_cal_component_get_alarm(component, ai->auid);
568+ if (a == nullptr)
569+ continue;
570+
571+ auto instance_time = std::make_pair(DateTime{gtz, ai->occur_start},
572+ DateTime{gtz, ai->occur_end});
573+ auto trigger_time = DateTime{gtz, ai->trigger};
574+
575+ auto& alarm = alarms[instance_time][trigger_time];
576+
577+ if (alarm.text.empty())
578+ alarm.text = get_alarm_text(a);
579+ if (alarm.audio_url.empty())
580+ alarm.audio_url = get_alarm_sound_url(a);
581+ if (!alarm.time.is_set())
582+ alarm.time = trigger_time;
583+
584+ e_cal_component_alarm_free(a);
585+ }
586+
587+ for (auto& i : alarms)
588+ {
589+ Appointment appointment = baseline;
590+ appointment.begin = i.first.first;
591+ appointment.end = i.first.second;
592+ appointment.alarms.reserve(i.second.size());
593+ for (auto& j : i.second)
594+ appointment.alarms.push_back(j.second);
595+ subtask->task->appointments.push_back(appointment);
596+ }
597 }
598
599 /***
600
601=== modified file 'tests/CMakeLists.txt'
602--- tests/CMakeLists.txt 2015-05-08 00:08:13 +0000
603+++ tests/CMakeLists.txt 2015-05-21 12:47:48 +0000
604@@ -78,25 +78,26 @@
605 set(EVOLUTION_SOURCE_SERVICE_NAME "org.gnome.evolution.dataserver.Sources3")
606 endif()
607
608-function(add_eds_test_by_name name)
609+function(add_eds_ics_test_by_name name)
610 set (TEST_NAME ${name})
611 add_executable(${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled)
612 target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
613 add_test (${TEST_NAME}
614- ${CMAKE_CURRENT_SOURCE_DIR}/run-eds-test.sh
615- ${DBUS_RUNNER} # arg1: dbus-test-runner exec
616- ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME} # arg2: test executable path
617- ${TEST_NAME} # arg3: test name
618- ${EVOLUTION_CALENDAR_FACTORY} # arg4: evolution-calendar-factory exec
619- ${EVOLUTION_SOURCE_SERVICE_NAME} # arg5: dbus name for source registry
620- ${EVOLUTION_SOURCE_REGISTRY} # arg6: evolution-source-registry exec
621- ${GVFSD} # arg7: gvfsd exec
622- ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}-config-files) # arg8: canned config files
623+ ${CMAKE_CURRENT_SOURCE_DIR}/run-eds-ics-test.sh
624+ ${DBUS_RUNNER} # arg1: dbus-test-runner exec
625+ ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME} # arg2: test executable path
626+ ${TEST_NAME} # arg3: test name
627+ ${EVOLUTION_CALENDAR_FACTORY} # arg4: evolution-calendar-factory exec
628+ ${EVOLUTION_SOURCE_SERVICE_NAME} # arg5: dbus name for source registry
629+ ${EVOLUTION_SOURCE_REGISTRY} # arg6: evolution-source-registry exec
630+ ${GVFSD} # arg7: gvfsd exec
631+ ${CMAKE_CURRENT_SOURCE_DIR}/test-eds-ics-config-files # arg8: base directory for config file template
632+ ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}.ics) # arg9: the ical file for this test
633 endfunction()
634-add_eds_test_by_name(test-eds-tasks)
635-add_eds_test_by_name(test-eds-valarms)
636-
637-
638+add_eds_ics_test_by_name(test-eds-ics-all-day-events)
639+add_eds_ics_test_by_name(test-eds-ics-repeating-events)
640+add_eds_ics_test_by_name(test-eds-ics-nonrepeating-events)
641+add_eds_ics_test_by_name(test-eds-ics-repeating-valarms)
642
643
644 # disabling the timezone unit tests because they require
645
646=== modified file 'tests/print-to.h'
647--- tests/print-to.h 2015-04-06 15:20:49 +0000
648+++ tests/print-to.h 2015-05-21 12:47:48 +0000
649@@ -33,9 +33,18 @@
650 ***/
651
652 void
653+PrintTo(const DateTime& datetime, std::ostream* os)
654+{
655+ *os << "{time:'" << datetime.format("%F %T %z") << '}';
656+}
657+
658+void
659 PrintTo(const Alarm& alarm, std::ostream* os)
660 {
661- *os << "{text:'" << alarm.text << "', audio_url:'" << alarm.audio_url << "', time:'"<<alarm.time.format("%F %T")<<"'}";
662+ *os << '{';
663+ *os << "{text:" << alarm.text << '}';
664+ PrintTo(alarm.time, os);
665+ *os << '}';
666 }
667
668 } // namespace datetime
669
670=== renamed file 'tests/run-eds-test.sh' => 'tests/run-eds-ics-test.sh'
671--- tests/run-eds-test.sh 2015-04-06 20:09:16 +0000
672+++ tests/run-eds-ics-test.sh 2015-05-21 12:47:48 +0000
673@@ -9,6 +9,7 @@
674 echo ARG6=$6 # full exectuable path of evolution-source-registry
675 echo ARG7=$7 # full executable path of gvfs
676 echo ARG8=$8 # config files
677+echo ARG8=$9 # ics file
678
679 # set up the tmpdir and tell the shell to purge it when we exit
680 export TEST_TMP_DIR=$(mktemp -p "${TMPDIR:-/tmp}" -d $3-XXXXXXXXXX) || exit 1
681@@ -32,6 +33,9 @@
682 export QORGANIZER_EDS_DEBUG=On
683 export GIO_USE_VFS=local # needed to ensure GVFS shuts down cleanly after the test is over
684
685+export G_MESSAGES_DEBUG=all
686+export G_DBUS_DEBUG=messages
687+
688 echo HOMEDIR=${HOME}
689 rm -rf ${XDG_DATA_HOME}
690
691@@ -41,6 +45,12 @@
692 cp --verbose --archive $8/. $HOME
693 fi
694
695+# if there's a specific ics file to test, copy it on top of the canned confilg files
696+if [ -e $9 ]; then
697+ echo "copying $9 into $HOME"
698+ cp --verbose --archive $9 ${XDG_DATA_HOME}/evolution/tasks/system/tasks.ics
699+fi
700+
701 # run dbus-test-runner
702 $1 --keep-env --max-wait=90 \
703 --task $2 --task-name $3 --wait-until-complete --wait-for=org.gnome.evolution.dataserver.Calendar4 \
704
705=== added file 'tests/test-eds-ics-all-day-events.cpp'
706--- tests/test-eds-ics-all-day-events.cpp 1970-01-01 00:00:00 +0000
707+++ tests/test-eds-ics-all-day-events.cpp 2015-05-21 12:47:48 +0000
708@@ -0,0 +1,91 @@
709+/*
710+ * Copyright 2015 Canonical Ltd.
711+ *
712+ * This program is free software: you can redistribute it and/or modify it
713+ * under the terms of the GNU General Public License version 3, as published
714+ * by the Free Software Foundation.
715+ *
716+ * This program is distributed in the hope that it will be useful, but
717+ * WITHOUT ANY WARRANTY; without even the implied warranties of
718+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
719+ * PURPOSE. See the GNU General Public License for more details.
720+ *
721+ * You should have received a copy of the GNU General Public License along
722+ * with this program. If not, see <http://www.gnu.org/licenses/>.
723+ *
724+ * Authors:
725+ * Charles Kerr <charles.kerr@canonical.com>
726+ */
727+
728+#include <algorithm>
729+
730+#include <datetime/alarm-queue-simple.h>
731+#include <datetime/clock-mock.h>
732+#include <datetime/engine-eds.h>
733+#include <datetime/planner-range.h>
734+
735+#include <gtest/gtest.h>
736+
737+#include "glib-fixture.h"
738+#include "print-to.h"
739+#include "timezone-mock.h"
740+#include "wakeup-timer-mock.h"
741+
742+using namespace unity::indicator::datetime;
743+using VAlarmFixture = GlibFixture;
744+
745+/***
746+****
747+***/
748+
749+TEST_F(VAlarmFixture, MultipleAppointments)
750+{
751+ // start the EDS engine
752+ auto engine = std::make_shared<EdsEngine>();
753+
754+ // we need a consistent timezone for the planner and our local DateTimes
755+ constexpr char const * zone_str {"America/Chicago"};
756+ auto tz = std::make_shared<MockTimezone>(zone_str);
757+ auto gtz = g_time_zone_new(zone_str);
758+
759+ // make a planner that looks at the first half of 2015 in EDS
760+ auto planner = std::make_shared<SimpleRangePlanner>(engine, tz);
761+ const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0};
762+ const DateTime range_end {gtz, 2015,6,31,23,59,59.5};
763+ planner->range().set(std::make_pair(range_begin, range_end));
764+
765+ // give EDS a moment to load
766+ if (planner->appointments().get().empty()) {
767+ g_message("waiting a moment for EDS to load...");
768+ auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){
769+ g_message("ah, they loaded");
770+ if (!appointments.empty())
771+ g_main_loop_quit(loop);
772+ };
773+ core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed));
774+ constexpr int max_wait_sec = 10;
775+ wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND);
776+ }
777+
778+ // what we expect to get...
779+ Appointment expected_appt;
780+ expected_appt.uid = "20150521T111538Z-7449-1000-3572-0@ghidorah";
781+ expected_appt.color = "#becedd";
782+ expected_appt.summary = "Memorial Day";
783+ expected_appt.begin = DateTime{gtz,2015,5,25,0,0,0};
784+ expected_appt.end = DateTime{gtz,2015,5,26,0,0,0};
785+
786+ // compare it to what we actually loaded...
787+ const auto appts = planner->appointments().get();
788+ ASSERT_EQ(1, appts.size());
789+ const auto& appt = appts[0];
790+ EXPECT_EQ(expected_appt.begin, appt.begin);
791+ EXPECT_EQ(expected_appt.end, appt.end);
792+ EXPECT_EQ(expected_appt.uid, appt.uid);
793+ EXPECT_EQ(expected_appt.color, appt.color);
794+ EXPECT_EQ(expected_appt.summary, appt.summary);
795+ EXPECT_EQ(0, appt.alarms.size());
796+
797+ // cleanup
798+ g_time_zone_unref(gtz);
799+}
800
801=== added file 'tests/test-eds-ics-all-day-events.ics'
802--- tests/test-eds-ics-all-day-events.ics 1970-01-01 00:00:00 +0000
803+++ tests/test-eds-ics-all-day-events.ics 2015-05-21 12:47:48 +0000
804@@ -0,0 +1,19 @@
805+BEGIN:VCALENDAR
806+CALSCALE:GREGORIAN
807+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
808+VERSION:2.0
809+X-EVOLUTION-DATA-REVISION:2015-05-07T21:14:49.315443Z(0)
810+BEGIN:VEVENT
811+UID:20150521T111538Z-7449-1000-3572-0@ghidorah
812+DTSTAMP:20150521T001128Z
813+DTSTART;VALUE=DATE:20150525
814+DTEND;VALUE=DATE:20150526
815+TRANSP:TRANSPARENT
816+SEQUENCE:2
817+SUMMARY:Memorial Day
818+DESCRIPTION:Today is Memorial Day
819+CLASS:PUBLIC
820+CREATED:20150521T111638Z
821+LAST-MODIFIED:20150521T111638Z
822+END:VEVENT
823+END:VCALENDAR
824
825=== added directory 'tests/test-eds-ics-config-files'
826=== added directory 'tests/test-eds-ics-config-files/.config'
827=== added directory 'tests/test-eds-ics-config-files/.config/evolution'
828=== added directory 'tests/test-eds-ics-config-files/.config/evolution/sources'
829=== added file 'tests/test-eds-ics-config-files/.config/evolution/sources/system-proxy.source'
830--- tests/test-eds-ics-config-files/.config/evolution/sources/system-proxy.source 1970-01-01 00:00:00 +0000
831+++ tests/test-eds-ics-config-files/.config/evolution/sources/system-proxy.source 2015-05-21 12:47:48 +0000
832@@ -0,0 +1,21 @@
833+
834+[Data Source]
835+DisplayName=Default Proxy Settings
836+Enabled=true
837+Parent=
838+
839+[Proxy]
840+Method=default
841+IgnoreHosts=localhost;127.0.0.0/8;::1;
842+AutoconfigUrl=
843+FtpHost=
844+FtpPort=0
845+HttpAuthPassword=
846+HttpAuthUser=
847+HttpHost=
848+HttpPort=8080
849+HttpUseAuth=false
850+HttpsHost=
851+HttpsPort=0
852+SocksHost=
853+SocksPort=0
854
855=== added directory 'tests/test-eds-ics-config-files/.local'
856=== added directory 'tests/test-eds-ics-config-files/.local/share'
857=== added directory 'tests/test-eds-ics-config-files/.local/share/evolution'
858=== added directory 'tests/test-eds-ics-config-files/.local/share/evolution/calendar'
859=== added directory 'tests/test-eds-ics-config-files/.local/share/evolution/calendar/system'
860=== added directory 'tests/test-eds-ics-config-files/.local/share/evolution/tasks'
861=== added directory 'tests/test-eds-ics-config-files/.local/share/evolution/tasks/system'
862=== added file 'tests/test-eds-ics-nonrepeating-events.cpp'
863--- tests/test-eds-ics-nonrepeating-events.cpp 1970-01-01 00:00:00 +0000
864+++ tests/test-eds-ics-nonrepeating-events.cpp 2015-05-21 12:47:48 +0000
865@@ -0,0 +1,93 @@
866+/*
867+ * Copyright 2015 Canonical Ltd.
868+ *
869+ * This program is free software: you can redistribute it and/or modify it
870+ * under the terms of the GNU General Public License version 3, as published
871+ * by the Free Software Foundation.
872+ *
873+ * This program is distributed in the hope that it will be useful, but
874+ * WITHOUT ANY WARRANTY; without even the implied warranties of
875+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
876+ * PURPOSE. See the GNU General Public License for more details.
877+ *
878+ * You should have received a copy of the GNU General Public License along
879+ * with this program. If not, see <http://www.gnu.org/licenses/>.
880+ *
881+ * Authors:
882+ * Charles Kerr <charles.kerr@canonical.com>
883+ */
884+
885+#include <algorithm>
886+
887+#include <datetime/alarm-queue-simple.h>
888+#include <datetime/clock-mock.h>
889+#include <datetime/engine-eds.h>
890+#include <datetime/planner-range.h>
891+
892+#include <gtest/gtest.h>
893+
894+#include "glib-fixture.h"
895+#include "print-to.h"
896+#include "timezone-mock.h"
897+#include "wakeup-timer-mock.h"
898+
899+using namespace unity::indicator::datetime;
900+using VAlarmFixture = GlibFixture;
901+
902+/***
903+****
904+***/
905+
906+TEST_F(VAlarmFixture, MultipleAppointments)
907+{
908+ // start the EDS engine
909+ auto engine = std::make_shared<EdsEngine>();
910+
911+ // we need a consistent timezone for the planner and our local DateTimes
912+ constexpr char const * zone_str {"America/Chicago"};
913+ auto tz = std::make_shared<MockTimezone>(zone_str);
914+ auto gtz = g_time_zone_new(zone_str);
915+
916+ // make a planner that looks at the first half of 2015 in EDS
917+ auto planner = std::make_shared<SimpleRangePlanner>(engine, tz);
918+ const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0};
919+ const DateTime range_end {gtz, 2015,6,31,23,59,59.5};
920+ planner->range().set(std::make_pair(range_begin, range_end));
921+
922+ // give EDS a moment to load
923+ if (planner->appointments().get().empty()) {
924+ g_message("waiting a moment for EDS to load...");
925+ auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){
926+ g_message("ah, they loaded");
927+ if (!appointments.empty())
928+ g_main_loop_quit(loop);
929+ };
930+ core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed));
931+ constexpr int max_wait_sec = 10;
932+ wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND);
933+ }
934+
935+ // what we expect to get...
936+ Appointment expected_appt;
937+ expected_appt.uid = "20150520T000726Z-3878-32011-1770-81@ubuntu-phablet";
938+ expected_appt.color = "#becedd";
939+ expected_appt.summary = "Alarm";
940+ std::array<Alarm,1> expected_alarms = {
941+ Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,20,20,00,0)})
942+ };
943+
944+ // compare it to what we actually loaded...
945+ const auto appts = planner->appointments().get();
946+ EXPECT_EQ(expected_alarms.size(), appts.size());
947+ for (size_t i=0, n=expected_alarms.size(); i<n; i++) {
948+ const auto& appt = appts[i];
949+ EXPECT_EQ(expected_appt.uid, appt.uid);
950+ EXPECT_EQ(expected_appt.color, appt.color);
951+ EXPECT_EQ(expected_appt.summary, appt.summary);
952+ EXPECT_EQ(1, appt.alarms.size());
953+ EXPECT_EQ(expected_alarms[i], appt.alarms[0]);
954+ }
955+
956+ // cleanup
957+ g_time_zone_unref(gtz);
958+}
959
960=== added file 'tests/test-eds-ics-nonrepeating-events.ics'
961--- tests/test-eds-ics-nonrepeating-events.ics 1970-01-01 00:00:00 +0000
962+++ tests/test-eds-ics-nonrepeating-events.ics 2015-05-21 12:47:48 +0000
963@@ -0,0 +1,27 @@
964+BEGIN:VCALENDAR
965+CALSCALE:GREGORIAN
966+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
967+VERSION:2.0
968+X-EVOLUTION-DATA-REVISION:2015-05-20T22:39:32.685099Z(1)
969+BEGIN:VTODO
970+UID:20150520T000726Z-3878-32011-1770-81@ubuntu-phablet
971+DTSTAMP:20150520T223932Z
972+DTSTART:20150520T200000
973+SUMMARY:Alarm
974+CATEGORIES:x-canonical-alarm
975+SEQUENCE:1
976+LAST-MODIFIED:20150520T223932Z
977+BEGIN:VALARM
978+X-EVOLUTION-ALARM-UID:20150520T223932Z-22506-32011-1771-2@ubuntu-phablet
979+ACTION:AUDIO
980+ATTACH:file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg
981+TRIGGER;VALUE=DURATION;RELATED=START:PT0S
982+END:VALARM
983+BEGIN:VALARM
984+X-EVOLUTION-ALARM-UID:20150520T223932Z-22506-32011-1771-3@ubuntu-phablet
985+ACTION:DISPLAY
986+DESCRIPTION:Alarm
987+TRIGGER;VALUE=DURATION;RELATED=START:PT0S
988+END:VALARM
989+END:VTODO
990+END:VCALENDAR
991
992=== added file 'tests/test-eds-ics-repeating-events.cpp'
993--- tests/test-eds-ics-repeating-events.cpp 1970-01-01 00:00:00 +0000
994+++ tests/test-eds-ics-repeating-events.cpp 2015-05-21 12:47:48 +0000
995@@ -0,0 +1,100 @@
996+/*
997+ * Copyright 2015 Canonical Ltd.
998+ *
999+ * This program is free software: you can redistribute it and/or modify it
1000+ * under the terms of the GNU General Public License version 3, as published
1001+ * by the Free Software Foundation.
1002+ *
1003+ * This program is distributed in the hope that it will be useful, but
1004+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1005+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1006+ * PURPOSE. See the GNU General Public License for more details.
1007+ *
1008+ * You should have received a copy of the GNU General Public License along
1009+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1010+ *
1011+ * Authors:
1012+ * Charles Kerr <charles.kerr@canonical.com>
1013+ */
1014+
1015+#include <algorithm>
1016+
1017+#include <datetime/alarm-queue-simple.h>
1018+#include <datetime/clock-mock.h>
1019+#include <datetime/engine-eds.h>
1020+#include <datetime/planner-range.h>
1021+
1022+#include <gtest/gtest.h>
1023+
1024+#include "glib-fixture.h"
1025+#include "print-to.h"
1026+#include "timezone-mock.h"
1027+#include "wakeup-timer-mock.h"
1028+
1029+using namespace unity::indicator::datetime;
1030+using VAlarmFixture = GlibFixture;
1031+
1032+/***
1033+****
1034+***/
1035+
1036+TEST_F(VAlarmFixture, MultipleAppointments)
1037+{
1038+ // start the EDS engine
1039+ auto engine = std::make_shared<EdsEngine>();
1040+
1041+ // we need a consistent timezone for the planner and our local DateTimes
1042+ constexpr char const * zone_str {"America/Chicago"};
1043+ auto tz = std::make_shared<MockTimezone>(zone_str);
1044+ auto gtz = g_time_zone_new(zone_str);
1045+
1046+ // make a planner that looks at the first half of 2015 in EDS
1047+ auto planner = std::make_shared<SimpleRangePlanner>(engine, tz);
1048+ const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0};
1049+ const DateTime range_end {gtz, 2015,6,31,23,59,59.5};
1050+ planner->range().set(std::make_pair(range_begin, range_end));
1051+
1052+ // give EDS a moment to load
1053+ if (planner->appointments().get().empty()) {
1054+ g_message("waiting a moment for EDS to load...");
1055+ auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){
1056+ g_message("ah, they loaded");
1057+ if (!appointments.empty())
1058+ g_main_loop_quit(loop);
1059+ };
1060+ core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed));
1061+ constexpr int max_wait_sec = 10;
1062+ wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND);
1063+ }
1064+
1065+ // what we expect to get...
1066+ Appointment expected_appt;
1067+ expected_appt.uid = "20150507T211449Z-4262-32011-1418-1@ubuntu-phablet";
1068+ expected_appt.color = "#becedd";
1069+ expected_appt.summary = "Alarm";
1070+ std::array<Alarm,8> expected_alarms = {
1071+ Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5, 8,16,40,0)}),
1072+ Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,15,16,40,0)}),
1073+ Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,22,16,40,0)}),
1074+ Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,29,16,40,0)}),
1075+ Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6, 5,16,40,0)}),
1076+ Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,12,16,40,0)}),
1077+ Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,19,16,40,0)}),
1078+ Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,26,16,40,0)})
1079+ };
1080+
1081+ // compare it to what we actually loaded...
1082+ const auto appts = planner->appointments().get();
1083+ EXPECT_EQ(expected_alarms.size(), appts.size());
1084+ for (size_t i=0, n=expected_alarms.size(); i<n; i++) {
1085+ const auto& appt = appts[i];
1086+ EXPECT_EQ(expected_appt.uid, appt.uid);
1087+ EXPECT_EQ(expected_appt.color, appt.color);
1088+ EXPECT_EQ(expected_appt.summary, appt.summary);
1089+ EXPECT_EQ(1, appt.alarms.size());
1090+ EXPECT_EQ(expected_alarms[i], appt.alarms[0]);
1091+ }
1092+
1093+ // cleanup
1094+ g_time_zone_unref(gtz);
1095+}
1096
1097=== added file 'tests/test-eds-ics-repeating-events.ics'
1098--- tests/test-eds-ics-repeating-events.ics 1970-01-01 00:00:00 +0000
1099+++ tests/test-eds-ics-repeating-events.ics 2015-05-21 12:47:48 +0000
1100@@ -0,0 +1,28 @@
1101+BEGIN:VCALENDAR
1102+CALSCALE:GREGORIAN
1103+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
1104+VERSION:2.0
1105+X-EVOLUTION-DATA-REVISION:2015-05-07T21:14:49.315443Z(0)
1106+BEGIN:VTODO
1107+UID:20150507T211449Z-4262-32011-1418-1@ubuntu-phablet
1108+DTSTAMP:20150508T211449Z
1109+DTSTART:20150508T164000
1110+RRULE:FREQ=WEEKLY;BYDAY=FR
1111+SUMMARY:Alarm
1112+CATEGORIES:x-canonical-alarm
1113+CREATED:20150507T211449Z
1114+LAST-MODIFIED:20150507T211449Z
1115+BEGIN:VALARM
1116+X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-2@ubuntu-phablet
1117+ACTION:AUDIO
1118+ATTACH:file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg
1119+TRIGGER;VALUE=DURATION;RELATED=START:PT0S
1120+END:VALARM
1121+BEGIN:VALARM
1122+X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-3@ubuntu-phablet
1123+ACTION:DISPLAY
1124+DESCRIPTION:Alarm
1125+TRIGGER;VALUE=DURATION;RELATED=START:PT0S
1126+END:VALARM
1127+END:VTODO
1128+END:VCALENDAR
1129
1130=== renamed file 'tests/test-eds-valarms.cpp' => 'tests/test-eds-ics-repeating-valarms.cpp'
1131=== added file 'tests/test-eds-ics-repeating-valarms.ics'
1132--- tests/test-eds-ics-repeating-valarms.ics 1970-01-01 00:00:00 +0000
1133+++ tests/test-eds-ics-repeating-valarms.ics 2015-05-21 12:47:48 +0000
1134@@ -0,0 +1,47 @@
1135+BEGIN:VCALENDAR
1136+CALSCALE:GREGORIAN
1137+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
1138+VERSION:2.0
1139+X-EVOLUTION-DATA-REVISION:2015-04-05T21:32:47.354433Z(2)
1140+BEGIN:VEVENT
1141+UID:20150405T213247Z-4371-32011-1698-1@ubuntu-phablet
1142+DTSTAMP:20150405T213247Z
1143+DTSTART:20150424T183500Z
1144+DTEND:20150424T193554Z
1145+X-LIC-ERROR;X-LIC-ERRORTYPE=VALUE-PARSE-ERROR:Can't parse as RECUR value
1146+ in RRULE property. Removing entire property: ERROR: No Value
1147+SUMMARY:London Sprint Flight
1148+CREATED:20150405T213247Z
1149+LAST-MODIFIED:20150405T213247Z
1150+BEGIN:VALARM
1151+X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-2@ubuntu-phablet
1152+ACTION:AUDIO
1153+TRIGGER;VALUE=DURATION;RELATED=START:-P1D
1154+REPEAT:3
1155+DURATION:PT2M
1156+END:VALARM
1157+BEGIN:VALARM
1158+X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-3@ubuntu-phablet
1159+ACTION:DISPLAY
1160+DESCRIPTION:Time to pack!
1161+TRIGGER;VALUE=DURATION;RELATED=START:-P1D
1162+REPEAT:3
1163+DURATION:PT2M
1164+END:VALARM
1165+BEGIN:VALARM
1166+X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-5@ubuntu-phablet
1167+ACTION:AUDIO
1168+TRIGGER;VALUE=DURATION;RELATED=START:-PT3H
1169+REPEAT:3
1170+DURATION:PT2M
1171+END:VALARM
1172+BEGIN:VALARM
1173+X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-6@ubuntu-phablet
1174+ACTION:DISPLAY
1175+DESCRIPTION:Go to the airport!
1176+TRIGGER;VALUE=DURATION;RELATED=START:-PT3H
1177+REPEAT:3
1178+DURATION:PT2M
1179+END:VALARM
1180+END:VEVENT
1181+END:VCALENDAR
1182
1183=== removed directory 'tests/test-eds-tasks-config-files'
1184=== removed directory 'tests/test-eds-tasks-config-files/.config'
1185=== removed directory 'tests/test-eds-tasks-config-files/.config/evolution'
1186=== removed directory 'tests/test-eds-tasks-config-files/.config/evolution/sources'
1187=== removed file 'tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source'
1188--- tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source 2015-05-08 00:08:13 +0000
1189+++ tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source 1970-01-01 00:00:00 +0000
1190@@ -1,21 +0,0 @@
1191-
1192-[Data Source]
1193-DisplayName=Default Proxy Settings
1194-Enabled=true
1195-Parent=
1196-
1197-[Proxy]
1198-Method=default
1199-IgnoreHosts=localhost;127.0.0.0/8;::1;
1200-AutoconfigUrl=
1201-FtpHost=
1202-FtpPort=0
1203-HttpAuthPassword=
1204-HttpAuthUser=
1205-HttpHost=
1206-HttpPort=8080
1207-HttpUseAuth=false
1208-HttpsHost=
1209-HttpsPort=0
1210-SocksHost=
1211-SocksPort=0
1212
1213=== removed directory 'tests/test-eds-tasks-config-files/.local'
1214=== removed directory 'tests/test-eds-tasks-config-files/.local/share'
1215=== removed directory 'tests/test-eds-tasks-config-files/.local/share/evolution'
1216=== removed directory 'tests/test-eds-tasks-config-files/.local/share/evolution/tasks'
1217=== removed directory 'tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system'
1218=== removed file 'tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics'
1219--- tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics 2015-05-08 00:08:13 +0000
1220+++ tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics 1970-01-01 00:00:00 +0000
1221@@ -1,28 +0,0 @@
1222-BEGIN:VCALENDAR
1223-CALSCALE:GREGORIAN
1224-PRODID:-//Ximian//NONSGML Evolution Calendar//EN
1225-VERSION:2.0
1226-X-EVOLUTION-DATA-REVISION:2015-05-07T21:14:49.315443Z(0)
1227-BEGIN:VTODO
1228-UID:20150507T211449Z-4262-32011-1418-1@ubuntu-phablet
1229-DTSTAMP:20150508T211449Z
1230-DTSTART:20150508T164000
1231-RRULE:FREQ=WEEKLY;BYDAY=FR
1232-SUMMARY:Alarm
1233-CATEGORIES:x-canonical-alarm
1234-CREATED:20150507T211449Z
1235-LAST-MODIFIED:20150507T211449Z
1236-BEGIN:VALARM
1237-X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-2@ubuntu-phablet
1238-ACTION:AUDIO
1239-ATTACH:file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg
1240-TRIGGER;VALUE=DURATION;RELATED=START:PT0S
1241-END:VALARM
1242-BEGIN:VALARM
1243-X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-3@ubuntu-phablet
1244-ACTION:DISPLAY
1245-DESCRIPTION:Alarm
1246-TRIGGER;VALUE=DURATION;RELATED=START:PT0S
1247-END:VALARM
1248-END:VTODO
1249-END:VCALENDAR
1250
1251=== removed file 'tests/test-eds-tasks.cpp'
1252--- tests/test-eds-tasks.cpp 2015-05-08 00:08:13 +0000
1253+++ tests/test-eds-tasks.cpp 1970-01-01 00:00:00 +0000
1254@@ -1,101 +0,0 @@
1255-/*
1256- * Copyright 2015 Canonical Ltd.
1257- *
1258- * This program is free software: you can redistribute it and/or modify it
1259- * under the terms of the GNU General Public License version 3, as published
1260- * by the Free Software Foundation.
1261- *
1262- * This program is distributed in the hope that it will be useful, but
1263- * WITHOUT ANY WARRANTY; without even the implied warranties of
1264- * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1265- * PURPOSE. See the GNU General Public License for more details.
1266- *
1267- * You should have received a copy of the GNU General Public License along
1268- * with this program. If not, see <http://www.gnu.org/licenses/>.
1269- *
1270- * Authors:
1271- * Charles Kerr <charles.kerr@canonical.com>
1272- */
1273-
1274-#include <algorithm>
1275-
1276-#include <datetime/alarm-queue-simple.h>
1277-#include <datetime/clock-mock.h>
1278-#include <datetime/engine-eds.h>
1279-#include <datetime/planner-range.h>
1280-
1281-#include <gtest/gtest.h>
1282-
1283-#include "glib-fixture.h"
1284-#include "print-to.h"
1285-#include "timezone-mock.h"
1286-#include "wakeup-timer-mock.h"
1287-
1288-using namespace unity::indicator::datetime;
1289-using VAlarmFixture = GlibFixture;
1290-
1291-/***
1292-****
1293-***/
1294-
1295-TEST_F(VAlarmFixture, MultipleAppointments)
1296-{
1297- // start the EDS engine
1298- auto engine = std::make_shared<EdsEngine>();
1299-
1300- // we need a consistent timezone for the planner and our local DateTimes
1301- constexpr char const * zone_str {"America/Chicago"};
1302- auto tz = std::make_shared<MockTimezone>(zone_str);
1303- auto gtz = g_time_zone_new(zone_str);
1304-
1305- // make a planner that looks at the first half of 2015 in EDS
1306- auto planner = std::make_shared<SimpleRangePlanner>(engine, tz);
1307- const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0};
1308- const DateTime range_end {gtz, 2015,6,31,23,59,59.5};
1309- planner->range().set(std::make_pair(range_begin, range_end));
1310-
1311- // give EDS a moment to load
1312- if (planner->appointments().get().empty()) {
1313- g_message("waiting a moment for EDS to load...");
1314- auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){
1315- g_message("ah, they loaded");
1316- if (!appointments.empty())
1317- g_main_loop_quit(loop);
1318- };
1319- core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed));
1320- constexpr int max_wait_sec = 10;
1321- wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND);
1322- }
1323-
1324- // what we expect to get...
1325- Appointment expected_appt;
1326- expected_appt.uid = "20150507T211449Z-4262-32011-1418-1@ubuntu-phablet";
1327- expected_appt.color = "#becedd";
1328- expected_appt.summary = "Alarm";
1329- std::array<Alarm,8> expected_alarms = {
1330- Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5, 8,16,40,0)}),
1331- Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,15,16,40,0)}),
1332- Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,22,16,40,0)}),
1333- Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,29,16,40,0)}),
1334- Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6, 5,16,40,0)}),
1335- Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,12,16,40,0)}),
1336- Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,19,16,40,0)}),
1337- Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,26,16,40,0)})
1338- };
1339-
1340- // compare it to what we actually loaded...
1341- const auto appts = planner->appointments().get();
1342- EXPECT_EQ(expected_alarms.size(), appts.size());
1343- for (size_t i=0, n=expected_alarms.size(); i<n; i++) {
1344- const auto& appt = appts[i];
1345- EXPECT_EQ(expected_appt.uid, appt.uid);
1346- EXPECT_EQ(expected_appt.color, appt.color);
1347- EXPECT_EQ(expected_appt.summary, appt.summary);
1348- EXPECT_EQ(1, appt.alarms.size());
1349- EXPECT_EQ(expected_alarms[i], appt.alarms[0]);
1350- }
1351-
1352-
1353- // cleanup
1354- g_time_zone_unref(gtz);
1355-}
1356
1357=== removed directory 'tests/test-eds-valarms-config-files'
1358=== removed directory 'tests/test-eds-valarms-config-files/.config'
1359=== removed directory 'tests/test-eds-valarms-config-files/.config/evolution'
1360=== removed directory 'tests/test-eds-valarms-config-files/.config/evolution/sources'
1361=== removed file 'tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source'
1362--- tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source 2015-04-06 00:19:01 +0000
1363+++ tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source 1970-01-01 00:00:00 +0000
1364@@ -1,21 +0,0 @@
1365-
1366-[Data Source]
1367-DisplayName=Default Proxy Settings
1368-Enabled=true
1369-Parent=
1370-
1371-[Proxy]
1372-Method=default
1373-IgnoreHosts=localhost;127.0.0.0/8;::1;
1374-AutoconfigUrl=
1375-FtpHost=
1376-FtpPort=0
1377-HttpAuthPassword=
1378-HttpAuthUser=
1379-HttpHost=
1380-HttpPort=8080
1381-HttpUseAuth=false
1382-HttpsHost=
1383-HttpsPort=0
1384-SocksHost=
1385-SocksPort=0
1386
1387=== removed directory 'tests/test-eds-valarms-config-files/.local'
1388=== removed directory 'tests/test-eds-valarms-config-files/.local/share'
1389=== removed directory 'tests/test-eds-valarms-config-files/.local/share/evolution'
1390=== removed directory 'tests/test-eds-valarms-config-files/.local/share/evolution/calendar'
1391=== removed directory 'tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system'
1392=== removed file 'tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics'
1393--- tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics 2015-04-06 00:19:01 +0000
1394+++ tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics 1970-01-01 00:00:00 +0000
1395@@ -1,47 +0,0 @@
1396-BEGIN:VCALENDAR
1397-CALSCALE:GREGORIAN
1398-PRODID:-//Ximian//NONSGML Evolution Calendar//EN
1399-VERSION:2.0
1400-X-EVOLUTION-DATA-REVISION:2015-04-05T21:32:47.354433Z(2)
1401-BEGIN:VEVENT
1402-UID:20150405T213247Z-4371-32011-1698-1@ubuntu-phablet
1403-DTSTAMP:20150405T213247Z
1404-DTSTART:20150424T183500Z
1405-DTEND:20150424T193554Z
1406-X-LIC-ERROR;X-LIC-ERRORTYPE=VALUE-PARSE-ERROR:Can't parse as RECUR value
1407- in RRULE property. Removing entire property: ERROR: No Value
1408-SUMMARY:London Sprint Flight
1409-CREATED:20150405T213247Z
1410-LAST-MODIFIED:20150405T213247Z
1411-BEGIN:VALARM
1412-X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-2@ubuntu-phablet
1413-ACTION:AUDIO
1414-TRIGGER;VALUE=DURATION;RELATED=START:-P1D
1415-REPEAT:3
1416-DURATION:PT2M
1417-END:VALARM
1418-BEGIN:VALARM
1419-X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-3@ubuntu-phablet
1420-ACTION:DISPLAY
1421-DESCRIPTION:Time to pack!
1422-TRIGGER;VALUE=DURATION;RELATED=START:-P1D
1423-REPEAT:3
1424-DURATION:PT2M
1425-END:VALARM
1426-BEGIN:VALARM
1427-X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-5@ubuntu-phablet
1428-ACTION:AUDIO
1429-TRIGGER;VALUE=DURATION;RELATED=START:-PT3H
1430-REPEAT:3
1431-DURATION:PT2M
1432-END:VALARM
1433-BEGIN:VALARM
1434-X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-6@ubuntu-phablet
1435-ACTION:DISPLAY
1436-DESCRIPTION:Go to the airport!
1437-TRIGGER;VALUE=DURATION;RELATED=START:-PT3H
1438-REPEAT:3
1439-DURATION:PT2M
1440-END:VALARM
1441-END:VEVENT
1442-END:VCALENDAR

Subscribers

People subscribed via source and target branches