From b115811bcafc3ce5193dc75cb7929a8f3f2cba00 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 24 Mar 2015 13:42:16 -0500 Subject: add support for an x-canonical-app-url x-prop when reading vtodos/vevents from EDS --- src/engine-eds.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 47c7a9b..8408a57 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -33,8 +33,10 @@ namespace unity { namespace indicator { namespace datetime { -static constexpr char const * TAG_ALARM {"x-canonical-alarm"}; -static constexpr char const * TAG_DISABLED {"x-canonical-disabled"}; +static constexpr char const * TAG_ALARM {"x-canonical-alarm"}; +static constexpr char const * TAG_DISABLED {"x-canonical-disabled"}; + +static constexpr char const * X_PROP_APP_URL {"x-canonical-app-url"}; /**** ***** @@ -469,6 +471,24 @@ private: if (text.value) appointment.summary = text.value; + auto icc = e_cal_component_get_icalcomponent(component); // component owns icc + if (icc) + { + auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY); + while (icalprop) + { + const char * x_name = icalproperty_get_x_name(icalprop); + if (!g_strcmp0(x_name, X_PROP_APP_URL)) + { + const char * url = icalproperty_get_value_as_string(icalprop); + if ((url != nullptr) && appointment.url.empty()) + appointment.url = url; + } + + icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY); + } + } + appointment.begin = begin_dt; appointment.end = end_dt; appointment.color = subtask->color; -- cgit v1.2.3 From d5002c6b27cf0e00828555a10e89778008d6cd01 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 24 Mar 2015 14:28:23 -0500 Subject: make the string test case-insensitive --- src/engine-eds.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 8408a57..a47ab4a 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -33,10 +33,10 @@ namespace unity { namespace indicator { namespace datetime { -static constexpr char const * TAG_ALARM {"x-canonical-alarm"}; -static constexpr char const * TAG_DISABLED {"x-canonical-disabled"}; +static constexpr char const * TAG_ALARM {"x-canonical-alarm"}; +static constexpr char const * TAG_DISABLED {"x-canonical-disabled"}; -static constexpr char const * X_PROP_APP_URL {"x-canonical-app-url"}; +static constexpr char const * X_PROP_ACTIVATION_URL {"X-CANONICAL-ACTIVATION-URL"}; /**** ***** @@ -478,7 +478,7 @@ private: while (icalprop) { const char * x_name = icalproperty_get_x_name(icalprop); - if (!g_strcmp0(x_name, X_PROP_APP_URL)) + if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL)) { const char * url = icalproperty_get_value_as_string(icalprop); if ((url != nullptr) && appointment.url.empty()) -- cgit v1.2.3 From 3ab790edf3280f1a5ff9ecf6cc89175c967ee0d3 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 9 Apr 2015 13:20:25 -0500 Subject: resolve minor shear with trunk --- src/engine-eds.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index b91fd71..e1424d7 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -542,6 +542,8 @@ private: auto icc = e_cal_component_get_icalcomponent(component); // component owns icc if (icc) { + g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak + auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY); while (icalprop) { @@ -549,8 +551,8 @@ private: if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL)) { const char * url = icalproperty_get_value_as_string(icalprop); - if ((url != nullptr) && appointment.url.empty()) - appointment.url = url; + if ((url != nullptr) && appointment.activation_url.empty()) + appointment.activation_url = url; } icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY); @@ -563,9 +565,6 @@ private: appointment.uid = uid; appointment.type = type; - icalcomponent * icc = e_cal_component_get_icalcomponent(component); - g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak - auto e_alarms = e_cal_util_generate_alarms_for_comp(component, subtask->begin, subtask->end, -- cgit v1.2.3 From 4d94be8b6cce52862a401ed39bad9209c71f9a3e Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 7 May 2015 19:08:13 -0500 Subject: Add unit test that looks at ubuntu alarm triggers. Test currently fails on the trigger timezone issue reported by alesage. --- tests/CMakeLists.txt | 1 + .../.config/evolution/sources/system-proxy.source | 21 +++++ .../.local/share/evolution/tasks/system/tasks.ics | 28 ++++++ tests/test-eds-tasks.cpp | 101 +++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source create mode 100644 tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics create mode 100644 tests/test-eds-tasks.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8b6ec5d..41efa06 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -93,6 +93,7 @@ function(add_eds_test_by_name name) ${GVFSD} # arg7: gvfsd exec ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}-config-files) # arg8: canned config files endfunction() +add_eds_test_by_name(test-eds-tasks) add_eds_test_by_name(test-eds-valarms) diff --git a/tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source b/tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source new file mode 100644 index 0000000..4b2f666 --- /dev/null +++ b/tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source @@ -0,0 +1,21 @@ + +[Data Source] +DisplayName=Default Proxy Settings +Enabled=true +Parent= + +[Proxy] +Method=default +IgnoreHosts=localhost;127.0.0.0/8;::1; +AutoconfigUrl= +FtpHost= +FtpPort=0 +HttpAuthPassword= +HttpAuthUser= +HttpHost= +HttpPort=8080 +HttpUseAuth=false +HttpsHost= +HttpsPort=0 +SocksHost= +SocksPort=0 diff --git a/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics b/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics new file mode 100644 index 0000000..efde5f7 --- /dev/null +++ b/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics @@ -0,0 +1,28 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2015-05-07T21:14:49.315443Z(0) +BEGIN:VTODO +UID:20150507T211449Z-4262-32011-1418-1@ubuntu-phablet +DTSTAMP:20150508T211449Z +DTSTART:20150508T164000 +RRULE:FREQ=WEEKLY;BYDAY=FR +SUMMARY:Alarm +CATEGORIES:x-canonical-alarm +CREATED:20150507T211449Z +LAST-MODIFIED:20150507T211449Z +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-2@ubuntu-phablet +ACTION:AUDIO +ATTACH:file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg +TRIGGER;VALUE=DURATION;RELATED=START:PT0S +END:VALARM +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-3@ubuntu-phablet +ACTION:DISPLAY +DESCRIPTION:Alarm +TRIGGER;VALUE=DURATION;RELATED=START:PT0S +END:VALARM +END:VTODO +END:VCALENDAR diff --git a/tests/test-eds-tasks.cpp b/tests/test-eds-tasks.cpp new file mode 100644 index 0000000..6dbb49f --- /dev/null +++ b/tests/test-eds-tasks.cpp @@ -0,0 +1,101 @@ +/* + * Copyright 2015 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include +#include +#include +#include + +#include + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace unity::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, MultipleAppointments) +{ + // start the EDS engine + auto engine = std::make_shared(); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Chicago"}; + auto tz = std::make_shared(zone_str); + auto gtz = g_time_zone_new(zone_str); + + // make a planner that looks at the first half of 2015 in EDS + auto planner = std::make_shared(engine, tz); + const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2015,6,31,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // what we expect to get... + Appointment expected_appt; + expected_appt.uid = "20150507T211449Z-4262-32011-1418-1@ubuntu-phablet"; + expected_appt.color = "#becedd"; + expected_appt.summary = "Alarm"; + std::array expected_alarms = { + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5, 8,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,15,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,22,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,29,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6, 5,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,12,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,19,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,26,16,40,0)}) + }; + + // compare it to what we actually loaded... + const auto appts = planner->appointments().get(); + EXPECT_EQ(expected_alarms.size(), appts.size()); + for (size_t i=0, n=expected_alarms.size(); i Date: Thu, 7 May 2015 19:10:37 -0500 Subject: in EngineEds' call to e_cal_util_generate_alarms_for_comp(), use nullptr as the default timezone so that we don't convert to local twice (once in _generate_alarms_for_comp, once in the DateTime ctor). Fixes the previous commit's failing test. --- src/engine-eds.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index e1424d7..9c1f7c4 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -571,7 +571,7 @@ private: const_cast(omit.data()), e_cal_client_resolve_tzid_cb, subtask->client, - subtask->default_timezone); + nullptr); std::map alarms; -- cgit v1.2.3 From 5a3c49ea09e971b6a2b1005de10171ebf165cf58 Mon Sep 17 00:00:00 2001 From: CI Train Bot Date: Fri, 8 May 2015 20:01:09 +0000 Subject: Releasing 13.10.0+15.04.20150508.2-0ubuntu1 --- debian/changelog | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/debian/changelog b/debian/changelog index 4ec36ff..940a4cf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +indicator-datetime (13.10.0+15.04.20150508.2-0ubuntu1) vivid; urgency=medium + + [ CI Train Bot ] + * New rebuild forced. + + [ Charles Kerr ] + * If the "X-CANONICAL-ACTIVATION-URL" x-prop is defined in the VTODO + or VEVENT, use it for url delegation. (LP: #1426519) + + -- CI Train Bot Fri, 08 May 2015 20:01:09 +0000 + indicator-datetime (13.10.0+15.04.20150406-0ubuntu1) vivid; urgency=medium [ Charles Kerr ] -- cgit v1.2.3 From 22084967d0e5ef0abbf0e2f798596f8eb6092266 Mon Sep 17 00:00:00 2001 From: CI Train Bot Date: Fri, 15 May 2015 19:33:55 +0000 Subject: Releasing 13.10.0+15.04.20150515-0ubuntu1 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index 940a4cf..33382d3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +indicator-datetime (13.10.0+15.04.20150515-0ubuntu1) vivid; urgency=medium + + [ Charles Kerr ] + * Honor the 'other vibrations' setting to enable/disable vibrations + when alarm notifications are shown. + + -- CI Train Bot Fri, 15 May 2015 19:33:54 +0000 + indicator-datetime (13.10.0+15.04.20150508.2-0ubuntu1) vivid; urgency=medium [ CI Train Bot ] -- cgit v1.2.3 From 991d36f01d0b341a9c8735a09de1eb488ca3db66 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 19 May 2015 08:19:00 -0500 Subject: undo last Friday's timezone regression. --- src/engine-eds.cpp | 2 +- tests/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 9c1f7c4..e1424d7 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -571,7 +571,7 @@ private: const_cast(omit.data()), e_cal_client_resolve_tzid_cb, subtask->client, - nullptr); + subtask->default_timezone); std::map alarms; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 41efa06..cd68dbf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -93,7 +93,7 @@ function(add_eds_test_by_name name) ${GVFSD} # arg7: gvfsd exec ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}-config-files) # arg8: canned config files endfunction() -add_eds_test_by_name(test-eds-tasks) +#add_eds_test_by_name(test-eds-tasks) add_eds_test_by_name(test-eds-valarms) -- cgit v1.2.3 From c0a9ccc02ef49a79f64cda1bad67521de3d6feb2 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 19 May 2015 19:02:09 -0500 Subject: add tracers. --- src/engine-eds.cpp | 28 ++++++++++++++---- tests/CMakeLists.txt | 34 +++++++++++----------- tests/run-eds-test.sh | 3 ++ .../.local/share/evolution/tasks/system/tasks.ics | 18 ++++++------ 4 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index e1424d7..e662b3d 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -506,12 +506,24 @@ private: const DateTime begin_dt { gtz, begin }; const DateTime end_dt { gtz, end }; - g_debug ("got appointment from %s to %s, uid %s status %d", - begin_dt.format("%F %T %z").c_str(), - end_dt.format("%F %T %z").c_str(), + g_debug ("got appointment from %zu (%s) to %zu (%s), uid %s status %d", + begin, begin_dt.format("%F %T %z").c_str(), + end, end_dt.format("%F %T %z").c_str(), uid, (int)status); +e_cal_component_commit_sequence(component); +g_debug("%s e_cal_component_commit_sequence: %s", G_STRLOC, e_cal_component_get_as_string(component)); // FIXME leaks +ECalComponentDateTime eccdt; +e_cal_component_get_dtstart(component, &eccdt); +const bool is_floating_time = eccdt.tzid == nullptr; +g_debug("%s is_floating_time is %d", G_STRLOC, (int)is_floating_time); +g_debug("%s dtstart is %s", G_STRLOC, icaltime_as_ical_string(*(eccdt.value))); +g_debug("%s icaltime_as_timet(dtstart) is %zu", G_STRLOC, icaltime_as_timet(*eccdt.value)); +g_debug("%s icaltime_as_timet_with_zone(dtstart, %s) is %zu", G_STRLOC, location, icaltime_as_timet_with_zone(*eccdt.value, subtask->default_timezone)); +g_debug("%s dtstart as a DateTime is %s", G_STRLOC, DateTime(gtz, icaltime_as_timet(*eccdt.value)).format("%F %T %z").c_str()); + + // look for the in-house tags bool disabled = false; Appointment::Type type = Appointment::EVENT; @@ -542,7 +554,7 @@ private: auto icc = e_cal_component_get_icalcomponent(component); // component owns icc if (icc) { - g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak + g_debug("%s icalcomponent_as_ical_string: %s", G_STRLOC, icalcomponent_as_ical_string(icc)); // libical owns this string; no leak auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY); while (icalprop) @@ -571,7 +583,7 @@ private: const_cast(omit.data()), e_cal_client_resolve_tzid_cb, subtask->client, - subtask->default_timezone); + is_floating_time ? nullptr : subtask->default_timezone); std::map alarms; @@ -582,6 +594,12 @@ private: auto ai = static_cast(l->data); auto a = e_cal_component_get_alarm(component, ai->auid); +g_debug("%s auid(%s), trigger(%zu), occur_start(%zu), occur_end(%zu)", G_STRLOC, ai->auid, ai->trigger, ai->occur_start, ai->occur_end); +g_debug("%s trigger as a DateTime('%s',%zu) is %s", G_STRLOC, location, ai->trigger, DateTime(gtz, ai->trigger).format("%F %T %z").c_str()); +auto local = DateTime::Local(ai->trigger); +g_debug("%s trigger as a DateTime::Local(%zu) is %s", G_STRLOC, ai->trigger, local.format("%F %T %z").c_str()); +g_debug("%s ..and then to_timezone('%s') is %s", G_STRLOC, location, local.to_timezone(location).format("%F %T %z").c_str()); + if (a != nullptr) { const DateTime alarm_begin{gtz, ai->trigger}; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cd68dbf..ecbda42 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -45,21 +45,21 @@ function(add_test_by_name name) add_test (${TEST_NAME} ${TEST_NAME}) target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) endfunction() -add_test_by_name(test-datetime) -add_test_by_name(test-snap) -add_test_by_name(test-actions) -add_test_by_name(test-alarm-queue) -add_test(NAME dear-reader-the-next-test-takes-60-seconds COMMAND true) -add_test_by_name(test-clock) -add_test_by_name(test-exporter) -add_test_by_name(test-formatter) -add_test_by_name(test-live-actions) -add_test_by_name(test-locations) -add_test_by_name(test-menus) -add_test_by_name(test-planner) -add_test_by_name(test-settings) -add_test_by_name(test-timezone-file) -add_test_by_name(test-utils) +#add_test_by_name(test-datetime) +#add_test_by_name(test-snap) +#add_test_by_name(test-actions) +#add_test_by_name(test-alarm-queue) +#add_test(NAME dear-reader-the-next-test-takes-60-seconds COMMAND true) +#add_test_by_name(test-clock) +#add_test_by_name(test-exporter) +#add_test_by_name(test-formatter) +#add_test_by_name(test-live-actions) +#add_test_by_name(test-locations) +#add_test_by_name(test-menus) +#add_test_by_name(test-planner) +#add_test_by_name(test-settings) +#add_test_by_name(test-timezone-file) +#add_test_by_name(test-utils) set (TEST_NAME manual-test-snap) add_executable (${TEST_NAME} ${TEST_NAME}.cpp) @@ -93,8 +93,8 @@ function(add_eds_test_by_name name) ${GVFSD} # arg7: gvfsd exec ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}-config-files) # arg8: canned config files endfunction() -#add_eds_test_by_name(test-eds-tasks) -add_eds_test_by_name(test-eds-valarms) +add_eds_test_by_name(test-eds-tasks) +#add_eds_test_by_name(test-eds-valarms) diff --git a/tests/run-eds-test.sh b/tests/run-eds-test.sh index 0183350..a9eceed 100755 --- a/tests/run-eds-test.sh +++ b/tests/run-eds-test.sh @@ -32,6 +32,9 @@ export XDG_VIDEOS_DIR=${TEST_TMP_DIR} export QORGANIZER_EDS_DEBUG=On export GIO_USE_VFS=local # needed to ensure GVFS shuts down cleanly after the test is over +export G_MESSAGES_DEBUG=all +export G_DBUS_DEBUG=messages + echo HOMEDIR=${HOME} rm -rf ${XDG_DATA_HOME} diff --git a/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics b/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics index efde5f7..1d6dc7a 100644 --- a/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics +++ b/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics @@ -2,24 +2,24 @@ BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//Ximian//NONSGML Evolution Calendar//EN VERSION:2.0 -X-EVOLUTION-DATA-REVISION:2015-05-07T21:14:49.315443Z(0) +X-EVOLUTION-DATA-REVISION:2015-05-19T22:52:41.612439Z(53) BEGIN:VTODO -UID:20150507T211449Z-4262-32011-1418-1@ubuntu-phablet -DTSTAMP:20150508T211449Z -DTSTART:20150508T164000 -RRULE:FREQ=WEEKLY;BYDAY=FR +UID:20150519T225233Z-3878-32011-1770-73@ubuntu-phablet +DTSTAMP:20150519T225241Z +DTSTART:20150520T120000 +RRULE:FREQ=DAILY SUMMARY:Alarm CATEGORIES:x-canonical-alarm -CREATED:20150507T211449Z -LAST-MODIFIED:20150507T211449Z +SEQUENCE:1 +LAST-MODIFIED:20150519T225241Z BEGIN:VALARM -X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-2@ubuntu-phablet +X-EVOLUTION-ALARM-UID:20150519T225241Z-3878-32011-1770-78@ubuntu-phablet ACTION:AUDIO ATTACH:file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg TRIGGER;VALUE=DURATION;RELATED=START:PT0S END:VALARM BEGIN:VALARM -X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-3@ubuntu-phablet +X-EVOLUTION-ALARM-UID:20150519T225241Z-3878-32011-1770-79@ubuntu-phablet ACTION:DISPLAY DESCRIPTION:Alarm TRIGGER;VALUE=DURATION;RELATED=START:PT0S -- cgit v1.2.3 From f9ac5ed383bbd1f8f7c56dc5a09f99e08556c823 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 19 May 2015 21:06:35 -0500 Subject: work directly with ical to generate alarm instances --- src/engine-eds.cpp | 414 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 375 insertions(+), 39 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index e662b3d..f541e44 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -79,9 +79,6 @@ public: const Timezone& timezone, std::function&)> func) { - const auto begin_timet = begin.to_unix(); - const auto end_timet = end.to_unix(); - const auto b_str = begin.format("%F %T"); const auto e_str = end.format("%F %T"); g_debug("getting all appointments from [%s ... %s]", b_str.c_str(), e_str.c_str()); @@ -133,15 +130,24 @@ public: client, color, default_timezone, - begin_timet, - end_timet); - e_cal_client_generate_instances(client, - begin_timet, - end_timet, - m_cancellable, - my_get_appointments_foreach, - subtask, - [](gpointer g){delete static_cast(g);}); + begin, + end); + + const auto begin_timet = begin.to_unix(); + const auto end_timet = end.to_unix(); + auto begin_str = isodate_from_time_t(begin_timet); + auto end_str = isodate_from_time_t(end_timet); + auto sexp = g_strdup_printf("(has-alarms-in-range? (make-time \"%s\") (make-time \"%s\"))", begin_str, end_str); + g_message("%s sexp is %s", G_STRLOC, sexp); + //e_cal_client_get_object_list(client, + e_cal_client_get_object_list_as_comps(client, + sexp, + m_cancellable, + on_object_list_ready, + subtask); + g_free(begin_str); + g_free(end_str); + g_free(sexp); } } @@ -419,24 +425,31 @@ private: ECalClient* client; std::string color; icaltimezone* default_timezone; - time_t begin; - time_t end; + GTimeZone * gtz; + DateTime begin; + DateTime end; AppointmentSubtask(const std::shared_ptr& task_in, ECalClient* client_in, const char* color_in, icaltimezone* default_tz, - time_t begin_, - time_t end_): + DateTime begin_, + DateTime end_): task(task_in), client(client_in), default_timezone(default_tz), + gtz(g_time_zone_new(icaltimezone_get_location(default_tz))), begin(begin_), end(end_) { if (color_in) color = color_in; } + + ~AppointmentSubtask() + { + g_clear_pointer(>z, g_time_zone_unref); + } }; static std::string get_alarm_text(ECalComponentAlarm * alarm) @@ -482,6 +495,346 @@ private: return ret; } + static void + on_object_list_ready(GObject * oclient, + GAsyncResult * res, + gpointer gsubtask) + { + GError * error = NULL; + GSList * comps = NULL; + + //if (e_cal_client_get_object_list_finish (E_CAL_CLIENT(oclient), res, &icalcomps, &error)) + if (e_cal_client_get_object_list_as_comps_finish (E_CAL_CLIENT(oclient), res, &comps, &error)) + { + auto subtask = static_cast(gsubtask); + + GSList * l; + for (l=comps; l!=nullptr; l=l->next) + add_component_to_task(subtask, static_cast(l->data)); + + e_cal_client_free_ecalcomp_slist(comps); + delete subtask; + } + else if (error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("indicator-datetime cannot mark one-time alarm as disabled: %s", error->message); + + g_error_free(error); + } + } + + static DateTime + datetime_from_component_date_time(const ECalComponentDateTime & in, + GTimeZone * default_timezone) + { + DateTime out; + + g_return_val_if_fail(in.value != nullptr, out); + + auto gtz = in.tzid == nullptr ? g_time_zone_ref(default_timezone) + : g_time_zone_new(in.tzid); + out = DateTime(gtz, + in.value->year, + in.value->month, + in.value->day, + in.value->hour, + in.value->minute, + in.value->second); + g_time_zone_unref(gtz); + return out; + } + +#if 0 + static DateTime + to_datetime(struct icaltimetype& t, GTimeZone* gtz) + { + auto t_utc = icaltime_convert_to_zone(t, icaltimezone_get_utc_timezone()); + return DateTime(gtz, icaltime_as_timet(t_utc)); + } +#endif + + static struct icaltimetype + icaltimetype_from_datetime(const DateTime& dt, icaltimezone * default_timezone) + { + return icaltime_from_timet_with_zone(dt.to_unix(), false, default_timezone); + } + + static DateTime + get_trigger_time (icalcomponent* recurrence_instance, icalcomponent* valarm, GTimeZone* gtz) + { + DateTime ret; + + g_debug("%s getting trigger time for valarm %s", G_STRLOC, icalcomponent_as_ical_string(valarm)); + + auto trigger_property = icalcomponent_get_first_property(valarm, ICAL_TRIGGER_PROPERTY); + if (trigger_property == nullptr) + return ret; + + auto trigger = icalproperty_get_trigger(trigger_property); + g_debug("%s found a trigger", G_STRLOC); + g_debug("%s trigger time is %s", G_STRLOC, icaltime_as_ical_string(trigger.time)); + g_debug("%s trigger duration is %s", G_STRLOC, icaldurationtype_as_ical_string(trigger.duration)); + fflush(NULL); + + struct icaltimetype tt; + if (!icaltime_is_null_time(trigger.time)) + { + tt = trigger.time; + } + else + { + g_debug("%s got a NULL time, so it's relative to the component"); + // TRIGGER;RELATED=END:P5M 5 minutes after END + // TRIGGER;RELATED=START:-P15M 15 minutes before START + // get RELATED parameter + const char * related = icalproperty_get_parameter_as_string(trigger_property, "RELATED"); + if (related == nullptr) + related = "START"; // default value + g_debug("%s got a RELATED value of %s", G_STRLOC, related); + + struct icaltimetype reference_point; + if (!g_strcmp0(related, "START")) + reference_point = icalcomponent_get_dtstart(recurrence_instance); + else if (!g_strcmp0(related, "END")) + reference_point = icalcomponent_get_dtend(recurrence_instance); + else + reference_point = icaltime_null_time(); + + g_debug("%s reference point is %s", G_STRLOC, icaltime_as_ical_string(reference_point)); + tt = icaltime_add(reference_point, trigger.duration); + g_debug("%s reference point + offset is %s", G_STRLOC, icaltime_as_ical_string(tt)); + } + + if (icaltime_is_valid_time(tt)) + { + if (tt.zone == NULL) // floating time + ret = DateTime(gtz, tt.year, tt.month, tt.day, tt.hour, tt.minute, tt.second); + else // convert it to UTC for our time_t ctor + ret = DateTime(gtz, icaltime_as_timet(icaltime_convert_to_zone(tt, icaltimezone_get_utc_timezone()))); + } + + return ret; + } + + static void + add_component_to_task(AppointmentSubtask * subtask, + ECalComponent * component) + { + // we only want calendar events and vtodos + const auto vtype = e_cal_component_get_vtype(component); + if ((vtype != E_CAL_COMPONENT_EVENT) && (vtype != E_CAL_COMPONENT_TODO)) + return; + + // FIXME: leaks + g_message("%s --> %s", G_STRLOC, e_cal_component_get_as_string (component)); + + // get uid + const gchar* uid = nullptr; + e_cal_component_get_uid(component, &uid); + + // get status + auto status = ICAL_STATUS_NONE; + e_cal_component_get_status(component, &status); + + // get default timezone + const char * location = icaltimezone_get_location(subtask->default_timezone); + auto gtz = g_time_zone_new(location); + + // get dtstart as a DateTime + ECalComponentDateTime eccdt_tmp; + e_cal_component_get_dtstart(component, &eccdt_tmp); + const auto begin = datetime_from_component_date_time(eccdt_tmp, gtz); + e_cal_component_free_datetime(&eccdt_tmp); + + // get dtend as a DateTime + e_cal_component_get_dtend(component, &eccdt_tmp); + DateTime end = eccdt_tmp.value != nullptr ? datetime_from_component_date_time(eccdt_tmp, gtz) : begin; + e_cal_component_free_datetime(&eccdt_tmp); + + g_debug("got appointment from %s to %s, uid %s status %d", + begin.format("%F %T %z").c_str(), + end.format("%F %T %z").c_str(), + uid, + (int)status); + + auto instance_callback = [](icalcomponent *comp, struct icaltime_span *span, void *vsubtask) { + auto instance = icalcomponent_new_clone(comp); + auto subtask = static_cast(vsubtask); + icalcomponent_set_dtstart(instance, icaltime_from_timet(span->start, false)); + icalcomponent_set_dtend(instance, icaltime_from_timet(span->end, false)); + g_debug("instance %s", icalcomponent_as_ical_string(instance)); + + auto valarm = icalcomponent_get_first_component(instance, ICAL_VALARM_COMPONENT); + while (valarm != nullptr) { + g_debug("valarm %s", icalcomponent_as_ical_string(valarm)); + auto trigger_property = icalcomponent_get_first_property(valarm, ICAL_TRIGGER_PROPERTY); + if (trigger_property != nullptr) + { + auto trigger = icalproperty_get_trigger(trigger_property); + g_debug("%s found a trigger", G_STRLOC); + g_debug("%s trigger time is %s", G_STRLOC, icaltime_as_ical_string(trigger.time)); + g_debug("%s trigger duration is %s", G_STRLOC, icaldurationtype_as_ical_string(trigger.duration)); + if(!icaltime_is_null_time(trigger.time)) + g_debug("value=DATE-TIME:%s\n", icaltime_as_ical_string(trigger.time)); + else + g_debug("value=DURATION:%s\n", icaldurationtype_as_ical_string(trigger.duration)); + } + g_debug("%s %p", G_STRLOC, subtask->gtz); + auto trigger_time = get_trigger_time (instance, valarm, subtask->gtz); + g_debug("%s", G_STRLOC); + if (trigger_time.is_set()) + { + g_debug("%s whoo got trigger time! %s", G_STRLOC, trigger_time.format("%F %T %z").c_str()); + } + valarm = icalcomponent_get_next_component(instance, ICAL_VALARM_COMPONENT); + } + +/* +(process:28936): Indicator-Datetime-DEBUG: valarm BEGIN:VALARM^M +X-EVOLUTION-ALARM-UID:20150519T235024Z-22031-1000-21745-12@ghidorah^M +DESCRIPTION:Summary^M +ACTION:DISPLAY^M +TRIGGER;VALUE=DURATION;RELATED=START:-PT15M^M +END:VALARM^M +*/ + }; + + auto rbegin = icaltimetype_from_datetime(subtask->begin, subtask->default_timezone); + auto rend = icaltimetype_from_datetime(subtask->end, subtask->default_timezone); + auto icc = e_cal_component_get_icalcomponent(component); // component owns icc + g_debug("calling foreach-recurrence... [%s...%s]", icaltime_as_ical_string(rbegin), icaltime_as_ical_string(rend)); + icalcomponent_foreach_recurrence(icc, rbegin, rend, instance_callback, subtask); +#if 0 + // look for the in-house tags + bool disabled = false; + Appointment::Type type = Appointment::EVENT; + GSList * categ_list = nullptr; + e_cal_component_get_categories_list (component, &categ_list); + for (GSList * l=categ_list; l!=nullptr; l=l->next) { + auto tag = static_cast(l->data); + if (!g_strcmp0(tag, TAG_ALARM)) + type = Appointment::UBUNTU_ALARM; + if (!g_strcmp0(tag, TAG_DISABLED)) + disabled = true; + } + e_cal_component_free_categories_list(categ_list); + + if ((uid != nullptr) && + (!disabled) && + (status != ICAL_STATUS_COMPLETED) && + (status != ICAL_STATUS_CANCELLED)) + { + constexpr std::array omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 + Appointment appointment; + + ECalComponentText text {}; + e_cal_component_get_summary(component, &text); + if (text.value) + appointment.summary = text.value; + + auto icc = e_cal_component_get_icalcomponent(component); // component owns icc + if (icc) + { + g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak + + auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY); + while (icalprop) + { + const char * x_name = icalproperty_get_x_name(icalprop); + if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL)) + { + const char * url = icalproperty_get_value_as_string(icalprop); + if ((url != nullptr) && appointment.activation_url.empty()) + appointment.activation_url = url; + } + + icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY); + } + } + + appointment.begin = begin; + appointment.end = end; + appointment.color = subtask->color; + appointment.uid = uid; + appointment.type = type; + + auto e_alarms = e_cal_util_generate_alarms_for_comp(component, + begin.to_unix(), + end.to_unix(), + const_cast(omit.data()), + e_cal_client_resolve_tzid_cb, + subtask->client, + nullptr); + + std::map alarms; + + if (e_alarms != nullptr) + { + for (auto l=e_alarms->alarms; l!=nullptr; l=l->next) + { + auto ai = static_cast(l->data); + auto a = e_cal_component_get_alarm(component, ai->auid); + + if (a != nullptr) + { + const DateTime alarm_begin{gtz, ai->trigger}; + auto& alarm = alarms[alarm_begin]; + + if (alarm.text.empty()) + alarm.text = get_alarm_text(a); + if (alarm.audio_url.empty()) + alarm.audio_url = get_alarm_sound_url(a); + if (!alarm.time.is_set()) + alarm.time = alarm_begin; + + e_cal_component_alarm_free(a); + } + } + + e_cal_component_alarms_free(e_alarms); + } + // Hm, no alarm triggers? + // That's a bug in alarms created by some versions of ubuntu-ui-toolkit. + // If that's what's happening here, let's handle those alarms anyway + // by effectively injecting a TRIGGER;VALUE=DURATION;RELATED=START:PT0S + else if (appointment.is_ubuntu_alarm()) + { + Alarm tmp; + tmp.time = appointment.begin; + + auto auids = e_cal_component_get_alarm_uids(component); + for(auto l=auids; l!=nullptr; l=l->next) + { + const auto auid = static_cast(l->data); + auto a = e_cal_component_get_alarm(component, auid); + if (a != nullptr) + { + if (tmp.text.empty()) + tmp.text = get_alarm_text(a); + if (tmp.audio_url.empty()) + tmp.audio_url = get_alarm_sound_url(a); + e_cal_component_alarm_free(a); + } + } + cal_obj_uid_list_free(auids); + + alarms[tmp.time] = tmp; + } + + appointment.alarms.reserve(alarms.size()); + for (const auto& it : alarms) + appointment.alarms.push_back(it.second); + + subtask->task->appointments.push_back(appointment); + } + + g_time_zone_unref(gtz); +#endif + } + + +#if 0 static gboolean my_get_appointments_foreach(ECalComponent* component, time_t begin, @@ -506,24 +859,12 @@ private: const DateTime begin_dt { gtz, begin }; const DateTime end_dt { gtz, end }; - g_debug ("got appointment from %zu (%s) to %zu (%s), uid %s status %d", - begin, begin_dt.format("%F %T %z").c_str(), - end, end_dt.format("%F %T %z").c_str(), + g_debug ("got appointment from %s to %s, uid %s status %d", + begin_dt.format("%F %T %z").c_str(), + end_dt.format("%F %T %z").c_str(), uid, (int)status); -e_cal_component_commit_sequence(component); -g_debug("%s e_cal_component_commit_sequence: %s", G_STRLOC, e_cal_component_get_as_string(component)); // FIXME leaks -ECalComponentDateTime eccdt; -e_cal_component_get_dtstart(component, &eccdt); -const bool is_floating_time = eccdt.tzid == nullptr; -g_debug("%s is_floating_time is %d", G_STRLOC, (int)is_floating_time); -g_debug("%s dtstart is %s", G_STRLOC, icaltime_as_ical_string(*(eccdt.value))); -g_debug("%s icaltime_as_timet(dtstart) is %zu", G_STRLOC, icaltime_as_timet(*eccdt.value)); -g_debug("%s icaltime_as_timet_with_zone(dtstart, %s) is %zu", G_STRLOC, location, icaltime_as_timet_with_zone(*eccdt.value, subtask->default_timezone)); -g_debug("%s dtstart as a DateTime is %s", G_STRLOC, DateTime(gtz, icaltime_as_timet(*eccdt.value)).format("%F %T %z").c_str()); - - // look for the in-house tags bool disabled = false; Appointment::Type type = Appointment::EVENT; @@ -554,7 +895,7 @@ g_debug("%s dtstart as a DateTime is %s", G_STRLOC, DateTime(gtz, icaltime_as_ti auto icc = e_cal_component_get_icalcomponent(component); // component owns icc if (icc) { - g_debug("%s icalcomponent_as_ical_string: %s", G_STRLOC, icalcomponent_as_ical_string(icc)); // libical owns this string; no leak + g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY); while (icalprop) @@ -583,7 +924,7 @@ g_debug("%s dtstart as a DateTime is %s", G_STRLOC, DateTime(gtz, icaltime_as_ti const_cast(omit.data()), e_cal_client_resolve_tzid_cb, subtask->client, - is_floating_time ? nullptr : subtask->default_timezone); + nullptr); std::map alarms; @@ -594,12 +935,6 @@ g_debug("%s dtstart as a DateTime is %s", G_STRLOC, DateTime(gtz, icaltime_as_ti auto ai = static_cast(l->data); auto a = e_cal_component_get_alarm(component, ai->auid); -g_debug("%s auid(%s), trigger(%zu), occur_start(%zu), occur_end(%zu)", G_STRLOC, ai->auid, ai->trigger, ai->occur_start, ai->occur_end); -g_debug("%s trigger as a DateTime('%s',%zu) is %s", G_STRLOC, location, ai->trigger, DateTime(gtz, ai->trigger).format("%F %T %z").c_str()); -auto local = DateTime::Local(ai->trigger); -g_debug("%s trigger as a DateTime::Local(%zu) is %s", G_STRLOC, ai->trigger, local.format("%F %T %z").c_str()); -g_debug("%s ..and then to_timezone('%s') is %s", G_STRLOC, location, local.to_timezone(location).format("%F %T %z").c_str()); - if (a != nullptr) { const DateTime alarm_begin{gtz, ai->trigger}; @@ -658,6 +993,7 @@ g_debug("%s ..and then to_timezone('%s') is %s", G_STRLOC, location, local.to_ti return G_SOURCE_CONTINUE; } +#endif /*** **** -- cgit v1.2.3 From 14660dc8578b63b86337fa1b00eb8c4c734354cf Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 20 May 2015 06:28:25 -0500 Subject: add stub for valarm repeat --- src/engine-eds.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index f541e44..53878b5 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -686,6 +686,13 @@ private: if (trigger_time.is_set()) { g_debug("%s whoo got trigger time! %s", G_STRLOC, trigger_time.format("%F %T %z").c_str()); + + auto duration_property = icalcomponent_get_first_property(valarm, ICAL_DURATION_PROPERTY); + auto repeat_property = icalcomponent_get_first_property(valarm, ICAL_DURATION_PROPERTY); + if ((duration_property != nullptr) && (repeat_property != nullptr)) + { + // FIXME: implement repeat + } } valarm = icalcomponent_get_next_component(instance, ICAL_VALARM_COMPONENT); } -- cgit v1.2.3 From 13ec568f7be0f3d9d3200334d331fee2ea39d9da Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 20 May 2015 06:37:06 -0500 Subject: what if we try calling generate_alarms() with the original component rather than the instance component... --- src/engine-eds.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 53878b5..adcdb63 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -141,10 +141,10 @@ public: g_message("%s sexp is %s", G_STRLOC, sexp); //e_cal_client_get_object_list(client, e_cal_client_get_object_list_as_comps(client, - sexp, - m_cancellable, - on_object_list_ready, - subtask); + sexp, + m_cancellable, + on_object_list_ready, + subtask); g_free(begin_str); g_free(end_str); g_free(sexp); @@ -658,6 +658,45 @@ private: uid, (int)status); + constexpr std::array omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 + auto e_alarms = e_cal_util_generate_alarms_for_comp (component, + subtask->begin.to_unix(), + subtask->end.to_unix(), + const_cast(omit.data()), + e_cal_client_resolve_tzid_cb, + subtask->client, + subtask->default_timezone); + if (e_alarms != nullptr) + { + for (auto l=e_alarms->alarms; l!=nullptr; l=l->next) + { + auto ai = static_cast(l->data); + auto a = e_cal_component_get_alarm(component, ai->auid); + + if (a != nullptr) + { +g_message("%s creating alarm_begin from ai->trigger %zu", G_STRLOC, (size_t)ai->trigger); + const DateTime alarm_begin{gtz, ai->trigger}; +g_message("%s alarm_begin is %s", alarm_begin.format("%F %T %z").c_str()); +#if 0 + auto& alarm = alarms[alarm_begin]; + + if (alarm.text.empty()) + alarm.text = get_alarm_text(a); + if (alarm.audio_url.empty()) + alarm.audio_url = get_alarm_sound_url(a); + if (!alarm.time.is_set()) + alarm.time = alarm_begin; +#endif + + e_cal_component_alarm_free(a); + } + } + + e_cal_component_alarms_free(e_alarms); + } + +#if 0 auto instance_callback = [](icalcomponent *comp, struct icaltime_span *span, void *vsubtask) { auto instance = icalcomponent_new_clone(comp); auto subtask = static_cast(vsubtask); @@ -712,6 +751,8 @@ END:VALARM^M auto icc = e_cal_component_get_icalcomponent(component); // component owns icc g_debug("calling foreach-recurrence... [%s...%s]", icaltime_as_ical_string(rbegin), icaltime_as_ical_string(rend)); icalcomponent_foreach_recurrence(icc, rbegin, rend, instance_callback, subtask); +#endif + #if 0 // look for the in-house tags bool disabled = false; -- cgit v1.2.3 From 03ad335029ca9c970ac3b7fbeab3f657e4718a71 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 20 May 2015 07:16:43 -0500 Subject: yes, generating alarms from the reference component, rather than the instance component, yields the right trigger timezones. Let's get to refactoring. --- src/engine-eds.cpp | 101 +++++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index adcdb63..5d423da 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -501,19 +501,38 @@ private: gpointer gsubtask) { GError * error = NULL; - GSList * comps = NULL; + GSList * comps_slist = NULL; - //if (e_cal_client_get_object_list_finish (E_CAL_CLIENT(oclient), res, &icalcomps, &error)) - if (e_cal_client_get_object_list_as_comps_finish (E_CAL_CLIENT(oclient), res, &comps, &error)) + if (e_cal_client_get_object_list_as_comps_finish (E_CAL_CLIENT(oclient), res, &comps_slist, &error)) { - auto subtask = static_cast(gsubtask); + // _generate_alarms takes a GList, so make a shallow copy of the comps GSList + GList * comps_list = nullptr; + for (auto l=comps_slist; l!=nullptr; l=l->next) + comps_list = g_list_prepend(comps_list, l->data); - GSList * l; - for (l=comps; l!=nullptr; l=l->next) - add_component_to_task(subtask, static_cast(l->data)); + constexpr std::array omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 + GSList * comp_alarms = nullptr; + auto subtask = static_cast(gsubtask); + e_cal_util_generate_alarms_for_list(comps_list, + subtask->begin.to_unix(), + subtask->end.to_unix(), + const_cast(omit.data()), + &comp_alarms, + e_cal_client_resolve_tzid_cb, + oclient, + subtask->default_timezone); + + // walk the alarms & add them + const char * location = icaltimezone_get_location(subtask->default_timezone); + auto gtz = g_time_zone_new(location); + for (auto l=comp_alarms; l!=nullptr; l=l->next) + add_alarms_to_subtask(static_cast(l->data), subtask, gtz); + g_time_zone_unref(gtz); - e_cal_client_free_ecalcomp_slist(comps); - delete subtask; + // cleanup + e_cal_free_alarms(comp_alarms); + g_list_free(comps_list); + e_cal_client_free_ecalcomp_slist(comps_slist); } else if (error != nullptr) { @@ -618,37 +637,34 @@ private: } static void - add_component_to_task(AppointmentSubtask * subtask, - ECalComponent * component) + add_alarms_to_subtask (ECalComponentAlarms * comp_alarms, + AppointmentSubtask * subtask, + GTimeZone * gtz) { // we only want calendar events and vtodos - const auto vtype = e_cal_component_get_vtype(component); + const auto vtype = e_cal_component_get_vtype(comp_alarms->comp); if ((vtype != E_CAL_COMPONENT_EVENT) && (vtype != E_CAL_COMPONENT_TODO)) return; // FIXME: leaks - g_message("%s --> %s", G_STRLOC, e_cal_component_get_as_string (component)); + g_message("%s --> %s", G_STRLOC, e_cal_component_get_as_string (comp_alarms->comp)); // get uid const gchar* uid = nullptr; - e_cal_component_get_uid(component, &uid); + e_cal_component_get_uid(comp_alarms->comp, &uid); // get status auto status = ICAL_STATUS_NONE; - e_cal_component_get_status(component, &status); - - // get default timezone - const char * location = icaltimezone_get_location(subtask->default_timezone); - auto gtz = g_time_zone_new(location); + e_cal_component_get_status(comp_alarms->comp, &status); // get dtstart as a DateTime ECalComponentDateTime eccdt_tmp; - e_cal_component_get_dtstart(component, &eccdt_tmp); + e_cal_component_get_dtstart(comp_alarms->comp, &eccdt_tmp); const auto begin = datetime_from_component_date_time(eccdt_tmp, gtz); e_cal_component_free_datetime(&eccdt_tmp); // get dtend as a DateTime - e_cal_component_get_dtend(component, &eccdt_tmp); + e_cal_component_get_dtend(comp_alarms->comp, &eccdt_tmp); DateTime end = eccdt_tmp.value != nullptr ? datetime_from_component_date_time(eccdt_tmp, gtz) : begin; e_cal_component_free_datetime(&eccdt_tmp); @@ -658,42 +674,29 @@ private: uid, (int)status); - constexpr std::array omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 - auto e_alarms = e_cal_util_generate_alarms_for_comp (component, - subtask->begin.to_unix(), - subtask->end.to_unix(), - const_cast(omit.data()), - e_cal_client_resolve_tzid_cb, - subtask->client, - subtask->default_timezone); - if (e_alarms != nullptr) + for (auto l=comp_alarms->alarms; l!=nullptr; l=l->next) { - for (auto l=e_alarms->alarms; l!=nullptr; l=l->next) - { - auto ai = static_cast(l->data); - auto a = e_cal_component_get_alarm(component, ai->auid); + auto ai = static_cast(l->data); + auto a = e_cal_component_get_alarm(comp_alarms->comp, ai->auid); - if (a != nullptr) - { + if (a != nullptr) + { g_message("%s creating alarm_begin from ai->trigger %zu", G_STRLOC, (size_t)ai->trigger); - const DateTime alarm_begin{gtz, ai->trigger}; + const DateTime alarm_begin{gtz, ai->trigger}; g_message("%s alarm_begin is %s", alarm_begin.format("%F %T %z").c_str()); #if 0 - auto& alarm = alarms[alarm_begin]; - - if (alarm.text.empty()) - alarm.text = get_alarm_text(a); - if (alarm.audio_url.empty()) - alarm.audio_url = get_alarm_sound_url(a); - if (!alarm.time.is_set()) - alarm.time = alarm_begin; + auto& alarm = alarms[alarm_begin]; + + if (alarm.text.empty()) + alarm.text = get_alarm_text(a); + if (alarm.audio_url.empty()) + alarm.audio_url = get_alarm_sound_url(a); + if (!alarm.time.is_set()) + alarm.time = alarm_begin; #endif - e_cal_component_alarm_free(a); - } + e_cal_component_alarm_free(a); } - - e_cal_component_alarms_free(e_alarms); } #if 0 -- cgit v1.2.3 From c91b1cc89bd83608e225d67983c69a9e08f93873 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 20 May 2015 08:07:15 -0500 Subject: first eds test passing --- src/engine-eds.cpp | 268 ++++++--------------- .../.local/share/evolution/tasks/system/tasks.ics | 18 +- tests/test-eds-tasks.cpp | 19 +- 3 files changed, 94 insertions(+), 211 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 5d423da..e49ba63 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -533,6 +533,7 @@ private: e_cal_free_alarms(comp_alarms); g_list_free(comps_list); e_cal_client_free_ecalcomp_slist(comps_slist); + delete subtask; } else if (error != nullptr) { @@ -641,50 +642,91 @@ private: AppointmentSubtask * subtask, GTimeZone * gtz) { + /** + *** Filter out components that we're not interested in + **/ + + auto& component = comp_alarms->comp; + // we only want calendar events and vtodos - const auto vtype = e_cal_component_get_vtype(comp_alarms->comp); + const auto vtype = e_cal_component_get_vtype(component); if ((vtype != E_CAL_COMPONENT_EVENT) && (vtype != E_CAL_COMPONENT_TODO)) return; - // FIXME: leaks - g_message("%s --> %s", G_STRLOC, e_cal_component_get_as_string (comp_alarms->comp)); + // we're not interested in completed or cancelled components + auto status = ICAL_STATUS_NONE; + e_cal_component_get_status(component, &status); + if ((status == ICAL_STATUS_COMPLETED) || (status == ICAL_STATUS_CANCELLED)) + return; - // get uid const gchar* uid = nullptr; - e_cal_component_get_uid(comp_alarms->comp, &uid); + e_cal_component_get_uid(component, &uid); + if (uid == nullptr) + return; - // get status - auto status = ICAL_STATUS_NONE; - e_cal_component_get_status(comp_alarms->comp, &status); + // walk through the cattegories list to look for our in-house tags + bool disabled = false; + Appointment::Type type = Appointment::EVENT; + GSList * categ_list = nullptr; + e_cal_component_get_categories_list (component, &categ_list); + for (GSList * l=categ_list; l!=nullptr; l=l->next) { + auto tag = static_cast(l->data); + if (!g_strcmp0(tag, TAG_ALARM)) + type = Appointment::UBUNTU_ALARM; + if (!g_strcmp0(tag, TAG_DISABLED)) + disabled = true; + } + e_cal_component_free_categories_list(categ_list); + if (disabled) // don't notify users about disabled alarms... + return; + + /** + *** This looks like a component that we should notify users about. + *** Let's add it. + **/ + + Appointment appointment; + appointment.uid = uid; + appointment.color = subtask->color; + appointment.type = type; + + // get appointment.summary + ECalComponentText text {}; + e_cal_component_get_summary(component, &text); + if (text.value) + appointment.summary = text.value; // get dtstart as a DateTime ECalComponentDateTime eccdt_tmp; - e_cal_component_get_dtstart(comp_alarms->comp, &eccdt_tmp); - const auto begin = datetime_from_component_date_time(eccdt_tmp, gtz); + e_cal_component_get_dtstart(component, &eccdt_tmp); + //const auto begin = datetime_from_component_date_time(eccdt_tmp, gtz); + appointment.begin = datetime_from_component_date_time(eccdt_tmp, gtz); e_cal_component_free_datetime(&eccdt_tmp); // get dtend as a DateTime - e_cal_component_get_dtend(comp_alarms->comp, &eccdt_tmp); - DateTime end = eccdt_tmp.value != nullptr ? datetime_from_component_date_time(eccdt_tmp, gtz) : begin; + e_cal_component_get_dtend(component, &eccdt_tmp); + //DateTime end = eccdt_tmp.value != nullptr ? datetime_from_component_date_time(eccdt_tmp, gtz) : begin; + appointment.end = eccdt_tmp.value != nullptr ? datetime_from_component_date_time(eccdt_tmp, gtz) : appointment.begin; e_cal_component_free_datetime(&eccdt_tmp); g_debug("got appointment from %s to %s, uid %s status %d", - begin.format("%F %T %z").c_str(), - end.format("%F %T %z").c_str(), + appointment.begin.format("%F %T %z").c_str(), + appointment.end.format("%F %T %z").c_str(), uid, (int)status); + // build our Alarm map from the comp_alarms + std::map alarms; for (auto l=comp_alarms->alarms; l!=nullptr; l=l->next) { auto ai = static_cast(l->data); - auto a = e_cal_component_get_alarm(comp_alarms->comp, ai->auid); + auto a = e_cal_component_get_alarm(component, ai->auid); if (a != nullptr) { g_message("%s creating alarm_begin from ai->trigger %zu", G_STRLOC, (size_t)ai->trigger); const DateTime alarm_begin{gtz, ai->trigger}; -g_message("%s alarm_begin is %s", alarm_begin.format("%F %T %z").c_str()); -#if 0 +g_message("%s alarm_begin is %s", G_STRLOC, alarm_begin.format("%F %T %z").c_str()); auto& alarm = alarms[alarm_begin]; if (alarm.text.empty()) @@ -693,195 +735,37 @@ g_message("%s alarm_begin is %s", alarm_begin.format("%F %T %z").c_str()); alarm.audio_url = get_alarm_sound_url(a); if (!alarm.time.is_set()) alarm.time = alarm_begin; -#endif e_cal_component_alarm_free(a); } } -#if 0 - auto instance_callback = [](icalcomponent *comp, struct icaltime_span *span, void *vsubtask) { - auto instance = icalcomponent_new_clone(comp); - auto subtask = static_cast(vsubtask); - icalcomponent_set_dtstart(instance, icaltime_from_timet(span->start, false)); - icalcomponent_set_dtend(instance, icaltime_from_timet(span->end, false)); - g_debug("instance %s", icalcomponent_as_ical_string(instance)); - - auto valarm = icalcomponent_get_first_component(instance, ICAL_VALARM_COMPONENT); - while (valarm != nullptr) { - g_debug("valarm %s", icalcomponent_as_ical_string(valarm)); - auto trigger_property = icalcomponent_get_first_property(valarm, ICAL_TRIGGER_PROPERTY); - if (trigger_property != nullptr) - { - auto trigger = icalproperty_get_trigger(trigger_property); - g_debug("%s found a trigger", G_STRLOC); - g_debug("%s trigger time is %s", G_STRLOC, icaltime_as_ical_string(trigger.time)); - g_debug("%s trigger duration is %s", G_STRLOC, icaldurationtype_as_ical_string(trigger.duration)); - if(!icaltime_is_null_time(trigger.time)) - g_debug("value=DATE-TIME:%s\n", icaltime_as_ical_string(trigger.time)); - else - g_debug("value=DURATION:%s\n", icaldurationtype_as_ical_string(trigger.duration)); - } - g_debug("%s %p", G_STRLOC, subtask->gtz); - auto trigger_time = get_trigger_time (instance, valarm, subtask->gtz); - g_debug("%s", G_STRLOC); - if (trigger_time.is_set()) - { - g_debug("%s whoo got trigger time! %s", G_STRLOC, trigger_time.format("%F %T %z").c_str()); - - auto duration_property = icalcomponent_get_first_property(valarm, ICAL_DURATION_PROPERTY); - auto repeat_property = icalcomponent_get_first_property(valarm, ICAL_DURATION_PROPERTY); - if ((duration_property != nullptr) && (repeat_property != nullptr)) - { - // FIXME: implement repeat - } - } - valarm = icalcomponent_get_next_component(instance, ICAL_VALARM_COMPONENT); - } - -/* -(process:28936): Indicator-Datetime-DEBUG: valarm BEGIN:VALARM^M -X-EVOLUTION-ALARM-UID:20150519T235024Z-22031-1000-21745-12@ghidorah^M -DESCRIPTION:Summary^M -ACTION:DISPLAY^M -TRIGGER;VALUE=DURATION;RELATED=START:-PT15M^M -END:VALARM^M -*/ - }; - - auto rbegin = icaltimetype_from_datetime(subtask->begin, subtask->default_timezone); - auto rend = icaltimetype_from_datetime(subtask->end, subtask->default_timezone); - auto icc = e_cal_component_get_icalcomponent(component); // component owns icc - g_debug("calling foreach-recurrence... [%s...%s]", icaltime_as_ical_string(rbegin), icaltime_as_ical_string(rend)); - icalcomponent_foreach_recurrence(icc, rbegin, rend, instance_callback, subtask); -#endif - -#if 0 - // look for the in-house tags - bool disabled = false; - Appointment::Type type = Appointment::EVENT; - GSList * categ_list = nullptr; - e_cal_component_get_categories_list (component, &categ_list); - for (GSList * l=categ_list; l!=nullptr; l=l->next) { - auto tag = static_cast(l->data); - if (!g_strcmp0(tag, TAG_ALARM)) - type = Appointment::UBUNTU_ALARM; - if (!g_strcmp0(tag, TAG_DISABLED)) - disabled = true; - } - e_cal_component_free_categories_list(categ_list); - - if ((uid != nullptr) && - (!disabled) && - (status != ICAL_STATUS_COMPLETED) && - (status != ICAL_STATUS_CANCELLED)) + // look for our recognized e-props + auto icc = e_cal_component_get_icalcomponent(component); // icc owned by component + if (icc != nullptr) { - constexpr std::array omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 - Appointment appointment; - - ECalComponentText text {}; - e_cal_component_get_summary(component, &text); - if (text.value) - appointment.summary = text.value; + g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak - auto icc = e_cal_component_get_icalcomponent(component); // component owns icc - if (icc) + auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY); + while (icalprop) { - g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak - - auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY); - while (icalprop) + const char * x_name = icalproperty_get_x_name(icalprop); + if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL)) { - const char * x_name = icalproperty_get_x_name(icalprop); - if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL)) - { - const char * url = icalproperty_get_value_as_string(icalprop); - if ((url != nullptr) && appointment.activation_url.empty()) - appointment.activation_url = url; - } - - icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY); + const char * url = icalproperty_get_value_as_string(icalprop); + if ((url != nullptr) && appointment.activation_url.empty()) + appointment.activation_url = url; } - } - appointment.begin = begin; - appointment.end = end; - appointment.color = subtask->color; - appointment.uid = uid; - appointment.type = type; - - auto e_alarms = e_cal_util_generate_alarms_for_comp(component, - begin.to_unix(), - end.to_unix(), - const_cast(omit.data()), - e_cal_client_resolve_tzid_cb, - subtask->client, - nullptr); - - std::map alarms; - - if (e_alarms != nullptr) - { - for (auto l=e_alarms->alarms; l!=nullptr; l=l->next) - { - auto ai = static_cast(l->data); - auto a = e_cal_component_get_alarm(component, ai->auid); - - if (a != nullptr) - { - const DateTime alarm_begin{gtz, ai->trigger}; - auto& alarm = alarms[alarm_begin]; - - if (alarm.text.empty()) - alarm.text = get_alarm_text(a); - if (alarm.audio_url.empty()) - alarm.audio_url = get_alarm_sound_url(a); - if (!alarm.time.is_set()) - alarm.time = alarm_begin; - - e_cal_component_alarm_free(a); - } - } - - e_cal_component_alarms_free(e_alarms); - } - // Hm, no alarm triggers? - // That's a bug in alarms created by some versions of ubuntu-ui-toolkit. - // If that's what's happening here, let's handle those alarms anyway - // by effectively injecting a TRIGGER;VALUE=DURATION;RELATED=START:PT0S - else if (appointment.is_ubuntu_alarm()) - { - Alarm tmp; - tmp.time = appointment.begin; - - auto auids = e_cal_component_get_alarm_uids(component); - for(auto l=auids; l!=nullptr; l=l->next) - { - const auto auid = static_cast(l->data); - auto a = e_cal_component_get_alarm(component, auid); - if (a != nullptr) - { - if (tmp.text.empty()) - tmp.text = get_alarm_text(a); - if (tmp.audio_url.empty()) - tmp.audio_url = get_alarm_sound_url(a); - e_cal_component_alarm_free(a); - } - } - cal_obj_uid_list_free(auids); - - alarms[tmp.time] = tmp; + icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY); } - - appointment.alarms.reserve(alarms.size()); - for (const auto& it : alarms) - appointment.alarms.push_back(it.second); - - subtask->task->appointments.push_back(appointment); } - g_time_zone_unref(gtz); -#endif + appointment.alarms.reserve(alarms.size()); + for (const auto& it : alarms) + appointment.alarms.push_back(it.second); + + subtask->task->appointments.push_back(appointment); } diff --git a/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics b/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics index 1d6dc7a..efde5f7 100644 --- a/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics +++ b/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics @@ -2,24 +2,24 @@ BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//Ximian//NONSGML Evolution Calendar//EN VERSION:2.0 -X-EVOLUTION-DATA-REVISION:2015-05-19T22:52:41.612439Z(53) +X-EVOLUTION-DATA-REVISION:2015-05-07T21:14:49.315443Z(0) BEGIN:VTODO -UID:20150519T225233Z-3878-32011-1770-73@ubuntu-phablet -DTSTAMP:20150519T225241Z -DTSTART:20150520T120000 -RRULE:FREQ=DAILY +UID:20150507T211449Z-4262-32011-1418-1@ubuntu-phablet +DTSTAMP:20150508T211449Z +DTSTART:20150508T164000 +RRULE:FREQ=WEEKLY;BYDAY=FR SUMMARY:Alarm CATEGORIES:x-canonical-alarm -SEQUENCE:1 -LAST-MODIFIED:20150519T225241Z +CREATED:20150507T211449Z +LAST-MODIFIED:20150507T211449Z BEGIN:VALARM -X-EVOLUTION-ALARM-UID:20150519T225241Z-3878-32011-1770-78@ubuntu-phablet +X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-2@ubuntu-phablet ACTION:AUDIO ATTACH:file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg TRIGGER;VALUE=DURATION;RELATED=START:PT0S END:VALARM BEGIN:VALARM -X-EVOLUTION-ALARM-UID:20150519T225241Z-3878-32011-1770-79@ubuntu-phablet +X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-3@ubuntu-phablet ACTION:DISPLAY DESCRIPTION:Alarm TRIGGER;VALUE=DURATION;RELATED=START:PT0S diff --git a/tests/test-eds-tasks.cpp b/tests/test-eds-tasks.cpp index 6dbb49f..2db1708 100644 --- a/tests/test-eds-tasks.cpp +++ b/tests/test-eds-tasks.cpp @@ -85,16 +85,15 @@ TEST_F(VAlarmFixture, MultipleAppointments) // compare it to what we actually loaded... const auto appts = planner->appointments().get(); - EXPECT_EQ(expected_alarms.size(), appts.size()); - for (size_t i=0, n=expected_alarms.size(); i Date: Wed, 20 May 2015 17:04:09 -0500 Subject: fixed first test passing --- src/engine-eds.cpp | 205 ++++++++++++++++++++++++++++++++++++----------- tests/test-eds-tasks.cpp | 18 ++--- 2 files changed, 165 insertions(+), 58 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index e49ba63..6302f06 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -138,7 +138,7 @@ public: auto begin_str = isodate_from_time_t(begin_timet); auto end_str = isodate_from_time_t(end_timet); auto sexp = g_strdup_printf("(has-alarms-in-range? (make-time \"%s\") (make-time \"%s\"))", begin_str, end_str); - g_message("%s sexp is %s", G_STRLOC, sexp); + g_debug("%s sexp is %s", G_STRLOC, sexp); //e_cal_client_get_object_list(client, e_cal_client_get_object_list_as_comps(client, sexp, @@ -572,7 +572,6 @@ private: auto t_utc = icaltime_convert_to_zone(t, icaltimezone_get_utc_timezone()); return DateTime(gtz, icaltime_as_timet(t_utc)); } -#endif static struct icaltimetype icaltimetype_from_datetime(const DateTime& dt, icaltimezone * default_timezone) @@ -636,17 +635,102 @@ private: return ret; } +#endif + +#if 0 +struct instance_data { + time_t start; + gboolean found; +}; + +static void +check_instance (icalcomponent *, + struct icaltime_span *span, + gpointer data) +{ + auto instance = static_cast(data); +auto start = icaltime_from_timet(span->start, false); +g_debug("%s span->start %s(%zu) instance->start %zu", G_STRLOC, icaltime_as_ical_string(start), (size_t)span->start, (size_t)instance->start); + + if (span->start == instance->start) + { +g_debug("%s FOUND!", G_STRLOC); + instance->found = TRUE; + } +} + +/** + * e_cal_util_construct_instance: + * @icalcomp: A recurring #icalcomponent + * @rid: The RECURRENCE-ID to construct a component for + * + * This checks that @rid indicates a valid recurrence of @icalcomp, and + * if so, generates a copy of @comp containing a RECURRENCE-ID of @rid. + * + * Returns: the instance, or %NULL. + **/ +static icalcomponent * +foo_cal_util_construct_instance (icalcomponent *icalcomp, + struct icaltimetype rid) +{ + struct instance_data instance; + struct icaltimetype start, end; + +g_debug("%s looking for instance at %s", G_STRLOC, icaltime_as_ical_string(rid)); +auto dtstart = icalcomponent_get_dtstart(icalcomp); +g_debug("%s in icalcomp whose dtstart is %s(%zu)", G_STRLOC, icaltime_as_ical_string(dtstart), (size_t)icaltime_as_timet(dtstart)); + + g_return_val_if_fail (icalcomp != NULL, NULL); + +g_debug("%s", G_STRLOC); + /* Make sure this is really recurring */ + if (!icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY) && + !icalcomponent_get_first_property (icalcomp, ICAL_RDATE_PROPERTY)) + return NULL; + +g_debug("%s", G_STRLOC); + /* Make sure the specified instance really exists */ + start = icaltime_convert_to_zone (rid, icaltimezone_get_utc_timezone ()); + instance.start = icaltime_as_timet(start); +g_debug("%s start %s (%zu)", G_STRLOC, icaltime_as_ical_string(start), instance.start); + end = start; + icaltime_adjust (&end, 0, 0, 0, 1); +g_debug("%s end %s", G_STRLOC, icaltime_as_ical_string(end)); + icaltime_adjust (&start, -30, 0, 0, 1); + icaltime_adjust (&end, 30, 0, 0, 1); +g_debug("%s start %s", G_STRLOC, icaltime_as_ical_string(start)); +g_debug("%s end %s", G_STRLOC, icaltime_as_ical_string(end)); + + //instance.start = icaltime_convert_to_zone (rid, icaltimezone_get_utc_timezone ()); + instance.found = FALSE; + icalcomponent_foreach_recurrence (icalcomp, start, end, + check_instance, &instance); + if (!instance.found) { +g_debug("%s not found", G_STRLOC); + return NULL; + } + + /* Make the instance */ + icalcomp = icalcomponent_new_clone (icalcomp); + icalcomponent_set_recurrenceid (icalcomp, rid); + + return icalcomp; +} +#endif + + + static void add_alarms_to_subtask (ECalComponentAlarms * comp_alarms, AppointmentSubtask * subtask, GTimeZone * gtz) { + auto& component = comp_alarms->comp; + /** *** Filter out components that we're not interested in **/ - - auto& component = comp_alarms->comp; // we only want calendar events and vtodos const auto vtype = e_cal_component_get_vtype(component); @@ -659,12 +743,7 @@ private: if ((status == ICAL_STATUS_COMPLETED) || (status == ICAL_STATUS_CANCELLED)) return; - const gchar* uid = nullptr; - e_cal_component_get_uid(component, &uid); - if (uid == nullptr) - return; - - // walk through the cattegories list to look for our in-house tags + // walk through the categories list to look for our in-house tags bool disabled = false; Appointment::Type type = Appointment::EVENT; GSList * categ_list = nullptr; @@ -685,67 +764,94 @@ private: *** Let's add it. **/ - Appointment appointment; - appointment.uid = uid; - appointment.color = subtask->color; - appointment.type = type; + Appointment reference_appointment; + reference_appointment.color = subtask->color; + reference_appointment.type = type; + + // get appointment.uid + const gchar* uid = nullptr; + e_cal_component_get_uid(component, &uid); + if (uid != nullptr) + reference_appointment.uid = uid; // get appointment.summary ECalComponentText text {}; e_cal_component_get_summary(component, &text); if (text.value) - appointment.summary = text.value; + reference_appointment.summary = text.value; - // get dtstart as a DateTime - ECalComponentDateTime eccdt_tmp; + // get appointment.begin + ECalComponentDateTime eccdt_tmp {}; e_cal_component_get_dtstart(component, &eccdt_tmp); - //const auto begin = datetime_from_component_date_time(eccdt_tmp, gtz); - appointment.begin = datetime_from_component_date_time(eccdt_tmp, gtz); + reference_appointment.begin = datetime_from_component_date_time(eccdt_tmp, gtz); e_cal_component_free_datetime(&eccdt_tmp); - // get dtend as a DateTime + // get appointment.end e_cal_component_get_dtend(component, &eccdt_tmp); - //DateTime end = eccdt_tmp.value != nullptr ? datetime_from_component_date_time(eccdt_tmp, gtz) : begin; - appointment.end = eccdt_tmp.value != nullptr ? datetime_from_component_date_time(eccdt_tmp, gtz) : appointment.begin; + reference_appointment.end = eccdt_tmp.value != nullptr + ? datetime_from_component_date_time(eccdt_tmp, gtz) + : reference_appointment.begin; e_cal_component_free_datetime(&eccdt_tmp); - g_debug("got appointment from %s to %s, uid %s status %d", - appointment.begin.format("%F %T %z").c_str(), - appointment.end.format("%F %T %z").c_str(), - uid, + g_debug("got appointment from %s to %s, status %d", + reference_appointment.begin.format("%F %T %z").c_str(), + reference_appointment.end.format("%F %T %z").c_str(), (int)status); // build our Alarm map from the comp_alarms - std::map alarms; + auto icc = e_cal_component_get_icalcomponent(component); // icc owned by component + g_debug("%s icc as ical string: %s", G_STRLOC, icalcomponent_as_ical_string(icc)); + + /* outer map key is the component instance's start + end time; we build Appointment.begin and .end from that. + * inner map key is the alarm trigger, we build Alarm.time from that. + * inner map value is the Alarm. + * + * we use the inner map to fold together multiple valarms that occur at the same time; + * e.g. one valarm may have a display action and another an audio action + */ + std::map,std::map> alarms; for (auto l=comp_alarms->alarms; l!=nullptr; l=l->next) { auto ai = static_cast(l->data); auto a = e_cal_component_get_alarm(component, ai->auid); - - if (a != nullptr) - { -g_message("%s creating alarm_begin from ai->trigger %zu", G_STRLOC, (size_t)ai->trigger); - const DateTime alarm_begin{gtz, ai->trigger}; -g_message("%s alarm_begin is %s", G_STRLOC, alarm_begin.format("%F %T %z").c_str()); - auto& alarm = alarms[alarm_begin]; - - if (alarm.text.empty()) - alarm.text = get_alarm_text(a); - if (alarm.audio_url.empty()) - alarm.audio_url = get_alarm_sound_url(a); - if (!alarm.time.is_set()) - alarm.time = alarm_begin; - - e_cal_component_alarm_free(a); - } + if (a == nullptr) + continue; + + const auto instance_time = std::make_pair(DateTime{gtz, ai->occur_start}, + DateTime{gtz, ai->occur_end}); + const auto trigger_time = DateTime{gtz, ai->trigger}; + +g_message("%s occur_start %zu(%s)", G_STRLOC, (size_t)ai->occur_start, instance_time.first.format("%F %T %z").c_str()); +g_message("%s occur_end %zu(%s)", G_STRLOC, (size_t)ai->occur_end, instance_time.second.format("%F %T %z").c_str()); +g_message("%s trigger_time %zu(%s)", G_STRLOC, (size_t)ai->trigger, trigger_time.format("%F %T %z").c_str()); + auto& alarm = alarms[instance_time][trigger_time]; + + if (alarm.text.empty()) + alarm.text = get_alarm_text(a); + if (alarm.audio_url.empty()) + alarm.audio_url = get_alarm_sound_url(a); + if (!alarm.time.is_set()) + alarm.time = trigger_time; + + e_cal_component_alarm_free(a); } - // look for our recognized e-props - auto icc = e_cal_component_get_icalcomponent(component); // icc owned by component - if (icc != nullptr) + // now build the appointments + for (auto& i : alarms) { - g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak + Appointment appointment = reference_appointment; + appointment.begin = i.first.first; + appointment.end = i.first.second; + appointment.alarms.reserve(i.second.size()); + for (auto& j : i.second) + appointment.alarms.push_back(j.second); + subtask->task->appointments.push_back(appointment); + } + // look for our recognized e-props + //if (icc != nullptr) + //{ +#if 0 auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY); while (icalprop) { @@ -759,13 +865,14 @@ g_message("%s alarm_begin is %s", G_STRLOC, alarm_begin.format("%F %T %z").c_str icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY); } - } + //} appointment.alarms.reserve(alarms.size()); for (const auto& it : alarms) appointment.alarms.push_back(it.second); subtask->task->appointments.push_back(appointment); +#endif } diff --git a/tests/test-eds-tasks.cpp b/tests/test-eds-tasks.cpp index 2db1708..526dcc6 100644 --- a/tests/test-eds-tasks.cpp +++ b/tests/test-eds-tasks.cpp @@ -85,15 +85,15 @@ TEST_F(VAlarmFixture, MultipleAppointments) // compare it to what we actually loaded... const auto appts = planner->appointments().get(); - ASSERT_GT(appts.size(), 0); - EXPECT_EQ(1, appts.size()); - const auto& appt = appts[0]; - EXPECT_EQ(expected_appt.uid, appt.uid); - EXPECT_EQ(expected_appt.color, appt.color); - EXPECT_EQ(expected_appt.summary, appt.summary); - EXPECT_EQ(expected_alarms.size(), appt.alarms.size()); - for (size_t i=0, n=expected_alarms.size(); i Date: Wed, 20 May 2015 18:23:32 -0500 Subject: Add an EDS test for nonrepeating alarms. Refactor the EDS testing structure slightly so that more of the basic config setup can be shared. --- tests/CMakeLists.txt | 28 +++--- tests/run-eds-ics-test.sh | 67 ++++++++++++++ tests/run-eds-test.sh | 60 ------------ .../.config/evolution/sources/system-proxy.source | 21 +++++ tests/test-eds-ics-nonrepeating-events.cpp | 93 +++++++++++++++++++ tests/test-eds-ics-nonrepeating-events.ics | 27 ++++++ tests/test-eds-ics-repeating-events.cpp | 100 ++++++++++++++++++++ tests/test-eds-ics-repeating-events.ics | 28 ++++++ tests/test-eds-ics-repeating-valarms.cpp | 101 +++++++++++++++++++++ tests/test-eds-ics-repeating-valarms.ics | 47 ++++++++++ .../.config/evolution/sources/system-proxy.source | 21 ----- .../.local/share/evolution/tasks/system/tasks.ics | 28 ------ tests/test-eds-tasks.cpp | 100 -------------------- .../.config/evolution/sources/system-proxy.source | 21 ----- .../share/evolution/calendar/system/calendar.ics | 47 ---------- tests/test-eds-valarms.cpp | 101 --------------------- 16 files changed, 498 insertions(+), 392 deletions(-) create mode 100755 tests/run-eds-ics-test.sh delete mode 100755 tests/run-eds-test.sh create mode 100644 tests/test-eds-ics-config-files/.config/evolution/sources/system-proxy.source create mode 100644 tests/test-eds-ics-nonrepeating-events.cpp create mode 100644 tests/test-eds-ics-nonrepeating-events.ics create mode 100644 tests/test-eds-ics-repeating-events.cpp create mode 100644 tests/test-eds-ics-repeating-events.ics create mode 100644 tests/test-eds-ics-repeating-valarms.cpp create mode 100644 tests/test-eds-ics-repeating-valarms.ics delete mode 100644 tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source delete mode 100644 tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics delete mode 100644 tests/test-eds-tasks.cpp delete mode 100644 tests/test-eds-valarms-config-files/.config/evolution/sources/system-proxy.source delete mode 100644 tests/test-eds-valarms-config-files/.local/share/evolution/calendar/system/calendar.ics delete mode 100644 tests/test-eds-valarms.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ecbda42..d0b88b2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,25 +78,25 @@ if(NOT EVOLUTION_SOURCE_SERVICE_NAME) set(EVOLUTION_SOURCE_SERVICE_NAME "org.gnome.evolution.dataserver.Sources3") endif() -function(add_eds_test_by_name name) +function(add_eds_ics_test_by_name name) set (TEST_NAME ${name}) add_executable(${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) add_test (${TEST_NAME} - ${CMAKE_CURRENT_SOURCE_DIR}/run-eds-test.sh - ${DBUS_RUNNER} # arg1: dbus-test-runner exec - ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME} # arg2: test executable path - ${TEST_NAME} # arg3: test name - ${EVOLUTION_CALENDAR_FACTORY} # arg4: evolution-calendar-factory exec - ${EVOLUTION_SOURCE_SERVICE_NAME} # arg5: dbus name for source registry - ${EVOLUTION_SOURCE_REGISTRY} # arg6: evolution-source-registry exec - ${GVFSD} # arg7: gvfsd exec - ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}-config-files) # arg8: canned config files + ${CMAKE_CURRENT_SOURCE_DIR}/run-eds-ics-test.sh + ${DBUS_RUNNER} # arg1: dbus-test-runner exec + ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME} # arg2: test executable path + ${TEST_NAME} # arg3: test name + ${EVOLUTION_CALENDAR_FACTORY} # arg4: evolution-calendar-factory exec + ${EVOLUTION_SOURCE_SERVICE_NAME} # arg5: dbus name for source registry + ${EVOLUTION_SOURCE_REGISTRY} # arg6: evolution-source-registry exec + ${GVFSD} # arg7: gvfsd exec + ${CMAKE_CURRENT_SOURCE_DIR}/test-eds-ics-config-files # arg8: base directory for config file template + ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}.ics) # arg9: the ical file for this test endfunction() -add_eds_test_by_name(test-eds-tasks) -#add_eds_test_by_name(test-eds-valarms) - - +add_eds_ics_test_by_name(test-eds-ics-repeating-events) +add_eds_ics_test_by_name(test-eds-ics-nonrepeating-events) +add_eds_ics_test_by_name(test-eds-ics-repeating-valarms) # disabling the timezone unit tests because they require diff --git a/tests/run-eds-ics-test.sh b/tests/run-eds-ics-test.sh new file mode 100755 index 0000000..0eeafad --- /dev/null +++ b/tests/run-eds-ics-test.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +echo ARG0=$0 # this script +echo ARG1=$1 # full executable path of dbus-test-runner +echo ARG2=$2 # full executable path of test app +echo ARG3=$3 # test name +echo ARG4=$4 # full executable path of evolution-calendar-factory +echo ARG5=$5 # bus service name of calendar factory +echo ARG6=$6 # full exectuable path of evolution-source-registry +echo ARG7=$7 # full executable path of gvfs +echo ARG8=$8 # config files +echo ARG8=$9 # ics file + +# set up the tmpdir and tell the shell to purge it when we exit +export TEST_TMP_DIR=$(mktemp -p "${TMPDIR:-/tmp}" -d $3-XXXXXXXXXX) || exit 1 +echo "running test '$3' in ${TEST_TMP_DIR}" + +# set up the environment variables +export QT_QPA_PLATFORM=minimal +export HOME=${TEST_TMP_DIR} +export XDG_RUNTIME_DIR=${TEST_TMP_DIR} +export XDG_CACHE_HOME=${TEST_TMP_DIR}/.cache +export XDG_CONFIG_HOME=${TEST_TMP_DIR}/.config +export XDG_DATA_HOME=${TEST_TMP_DIR}/.local/share +export XDG_DESKTOP_DIR=${TEST_TMP_DIR} +export XDG_DOCUMENTS_DIR=${TEST_TMP_DIR} +export XDG_DOWNLOAD_DIR=${TEST_TMP_DIR} +export XDG_MUSIC_DIR=${TEST_TMP_DIR} +export XDG_PICTURES_DIR=${TEST_TMP_DIR} +export XDG_PUBLICSHARE_DIR=${TEST_TMP_DIR} +export XDG_TEMPLATES_DIR=${TEST_TMP_DIR} +export XDG_VIDEOS_DIR=${TEST_TMP_DIR} +export QORGANIZER_EDS_DEBUG=On +export GIO_USE_VFS=local # needed to ensure GVFS shuts down cleanly after the test is over + +export G_MESSAGES_DEBUG=all +export G_DBUS_DEBUG=messages + +echo HOMEDIR=${HOME} +rm -rf ${XDG_DATA_HOME} + +# if there are canned config files for this test, move them into place now +if [ -d $8 ]; then + echo "copying files from $8 to $HOME" + cp --verbose --archive $8/. $HOME +fi + +# if there's a specific ics file to test, copy it on top of the canned confilg files +if [ -e $9 ]; then + echo "copying $9 into $HOME" + cp --verbose --archive $9 $HOME/.local/share/evolution/tasks/system/tasks.ics +fi + +# run dbus-test-runner +$1 --keep-env --max-wait=90 \ +--task $2 --task-name $3 --wait-until-complete --wait-for=org.gnome.evolution.dataserver.Calendar4 \ +--task $4 --task-name "evolution" --wait-until-complete -r +#--task $6 --task-name "source-registry" --wait-for=org.gtk.vfs.Daemon -r \ +#--task $7 --task-name "gvfsd" -r +rv=$? + +# if the test passed, blow away the tmpdir +if [ $rv -eq 0 ]; then + rm -rf $TEST_TMP_DIR +fi + +return $rv diff --git a/tests/run-eds-test.sh b/tests/run-eds-test.sh deleted file mode 100755 index a9eceed..0000000 --- a/tests/run-eds-test.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh - -echo ARG0=$0 # this script -echo ARG1=$1 # full executable path of dbus-test-runner -echo ARG2=$2 # full executable path of test app -echo ARG3=$3 # test name -echo ARG4=$4 # full executable path of evolution-calendar-factory -echo ARG5=$5 # bus service name of calendar factory -echo ARG6=$6 # full exectuable path of evolution-source-registry -echo ARG7=$7 # full executable path of gvfs -echo ARG8=$8 # config files - -# set up the tmpdir and tell the shell to purge it when we exit -export TEST_TMP_DIR=$(mktemp -p "${TMPDIR:-/tmp}" -d $3-XXXXXXXXXX) || exit 1 -echo "running test '$3' in ${TEST_TMP_DIR}" - -# set up the environment variables -export QT_QPA_PLATFORM=minimal -export HOME=${TEST_TMP_DIR} -export XDG_RUNTIME_DIR=${TEST_TMP_DIR} -export XDG_CACHE_HOME=${TEST_TMP_DIR}/.cache -export XDG_CONFIG_HOME=${TEST_TMP_DIR}/.config -export XDG_DATA_HOME=${TEST_TMP_DIR}/.local/share -export XDG_DESKTOP_DIR=${TEST_TMP_DIR} -export XDG_DOCUMENTS_DIR=${TEST_TMP_DIR} -export XDG_DOWNLOAD_DIR=${TEST_TMP_DIR} -export XDG_MUSIC_DIR=${TEST_TMP_DIR} -export XDG_PICTURES_DIR=${TEST_TMP_DIR} -export XDG_PUBLICSHARE_DIR=${TEST_TMP_DIR} -export XDG_TEMPLATES_DIR=${TEST_TMP_DIR} -export XDG_VIDEOS_DIR=${TEST_TMP_DIR} -export QORGANIZER_EDS_DEBUG=On -export GIO_USE_VFS=local # needed to ensure GVFS shuts down cleanly after the test is over - -export G_MESSAGES_DEBUG=all -export G_DBUS_DEBUG=messages - -echo HOMEDIR=${HOME} -rm -rf ${XDG_DATA_HOME} - -# if there are canned config files for this test, move them into place now -if [ -d $8 ]; then - echo "copying files from $8 to $HOME" - cp --verbose --archive $8/. $HOME -fi - -# run dbus-test-runner -$1 --keep-env --max-wait=90 \ ---task $2 --task-name $3 --wait-until-complete --wait-for=org.gnome.evolution.dataserver.Calendar4 \ ---task $4 --task-name "evolution" --wait-until-complete -r -#--task $6 --task-name "source-registry" --wait-for=org.gtk.vfs.Daemon -r \ -#--task $7 --task-name "gvfsd" -r -rv=$? - -# if the test passed, blow away the tmpdir -if [ $rv -eq 0 ]; then - rm -rf $TEST_TMP_DIR -fi - -return $rv diff --git a/tests/test-eds-ics-config-files/.config/evolution/sources/system-proxy.source b/tests/test-eds-ics-config-files/.config/evolution/sources/system-proxy.source new file mode 100644 index 0000000..4b2f666 --- /dev/null +++ b/tests/test-eds-ics-config-files/.config/evolution/sources/system-proxy.source @@ -0,0 +1,21 @@ + +[Data Source] +DisplayName=Default Proxy Settings +Enabled=true +Parent= + +[Proxy] +Method=default +IgnoreHosts=localhost;127.0.0.0/8;::1; +AutoconfigUrl= +FtpHost= +FtpPort=0 +HttpAuthPassword= +HttpAuthUser= +HttpHost= +HttpPort=8080 +HttpUseAuth=false +HttpsHost= +HttpsPort=0 +SocksHost= +SocksPort=0 diff --git a/tests/test-eds-ics-nonrepeating-events.cpp b/tests/test-eds-ics-nonrepeating-events.cpp new file mode 100644 index 0000000..a244f1e --- /dev/null +++ b/tests/test-eds-ics-nonrepeating-events.cpp @@ -0,0 +1,93 @@ +/* + * Copyright 2015 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include +#include +#include +#include + +#include + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace unity::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, MultipleAppointments) +{ + // start the EDS engine + auto engine = std::make_shared(); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Chicago"}; + auto tz = std::make_shared(zone_str); + auto gtz = g_time_zone_new(zone_str); + + // make a planner that looks at the first half of 2015 in EDS + auto planner = std::make_shared(engine, tz); + const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2015,6,31,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // what we expect to get... + Appointment expected_appt; + expected_appt.uid = "20150520T000726Z-3878-32011-1770-81@ubuntu-phablet"; + expected_appt.color = "#becedd"; + expected_appt.summary = "Alarm"; + std::array expected_alarms = { + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,20,20,00,0)}) + }; + + // compare it to what we actually loaded... + const auto appts = planner->appointments().get(); + EXPECT_EQ(expected_alarms.size(), appts.size()); + for (size_t i=0, n=expected_alarms.size(); i. + * + * Authors: + * Charles Kerr + */ + +#include + +#include +#include +#include +#include + +#include + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace unity::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, MultipleAppointments) +{ + // start the EDS engine + auto engine = std::make_shared(); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Chicago"}; + auto tz = std::make_shared(zone_str); + auto gtz = g_time_zone_new(zone_str); + + // make a planner that looks at the first half of 2015 in EDS + auto planner = std::make_shared(engine, tz); + const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2015,6,31,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // what we expect to get... + Appointment expected_appt; + expected_appt.uid = "20150507T211449Z-4262-32011-1418-1@ubuntu-phablet"; + expected_appt.color = "#becedd"; + expected_appt.summary = "Alarm"; + std::array expected_alarms = { + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5, 8,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,15,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,22,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,29,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6, 5,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,12,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,19,16,40,0)}), + Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,26,16,40,0)}) + }; + + // compare it to what we actually loaded... + const auto appts = planner->appointments().get(); + EXPECT_EQ(expected_alarms.size(), appts.size()); + for (size_t i=0, n=expected_alarms.size(); i. + * + * Authors: + * Charles Kerr + */ + +#include + +#include +#include +#include +#include + +#include + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace unity::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, MultipleAppointments) +{ + // start the EDS engine + auto engine = std::make_shared(); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Chicago"}; + auto tz = std::make_shared(zone_str); + auto gtz = g_time_zone_new(zone_str); + + // make a planner that looks at the first half of 2015 in EDS + auto planner = std::make_shared(engine, tz); + const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2015,6,31,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // the planner should match what we've got in the calendar.ics file + const auto appts = planner->appointments().get(); + ASSERT_EQ(1, appts.size()); + const auto& appt = appts.front(); + ASSERT_EQ(8, appt.alarms.size()); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,35,0)}), appt.alarms[0]); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,37,0)}), appt.alarms[1]); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,39,0)}), appt.alarms[2]); + EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,41,0)}), appt.alarms[3]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,35,0)}), appt.alarms[4]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,37,0)}), appt.alarms[5]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,39,0)}), appt.alarms[6]); + EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,41,0)}), appt.alarms[7]); + + // now let's try this out with AlarmQueue... + // hook the planner up to a SimpleAlarmQueue and confirm that it triggers for each of the reminders + auto mock_clock = std::make_shared(range_begin); + std::shared_ptr clock = mock_clock; + std::shared_ptr wakeup_timer = std::make_shared(clock); + auto alarm_queue = std::make_shared(clock, planner, wakeup_timer); + int triggered_count = 0; + alarm_queue->alarm_reached().connect([&triggered_count, appt](const Appointment&, const Alarm& active_alarm) { + EXPECT_TRUE(std::find(appt.alarms.begin(), appt.alarms.end(), active_alarm) != appt.alarms.end()); + ++triggered_count; + }); + for (auto now=range_begin; nowset_localtime(now); + EXPECT_EQ(appt.alarms.size(), triggered_count); + + // cleanup + g_time_zone_unref(gtz); +} diff --git a/tests/test-eds-ics-repeating-valarms.ics b/tests/test-eds-ics-repeating-valarms.ics new file mode 100644 index 0000000..fe526f4 --- /dev/null +++ b/tests/test-eds-ics-repeating-valarms.ics @@ -0,0 +1,47 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2015-04-05T21:32:47.354433Z(2) +BEGIN:VEVENT +UID:20150405T213247Z-4371-32011-1698-1@ubuntu-phablet +DTSTAMP:20150405T213247Z +DTSTART:20150424T183500Z +DTEND:20150424T193554Z +X-LIC-ERROR;X-LIC-ERRORTYPE=VALUE-PARSE-ERROR:Can't parse as RECUR value + in RRULE property. Removing entire property: ERROR: No Value +SUMMARY:London Sprint Flight +CREATED:20150405T213247Z +LAST-MODIFIED:20150405T213247Z +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-2@ubuntu-phablet +ACTION:AUDIO +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +REPEAT:3 +DURATION:PT2M +END:VALARM +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-3@ubuntu-phablet +ACTION:DISPLAY +DESCRIPTION:Time to pack! +TRIGGER;VALUE=DURATION;RELATED=START:-P1D +REPEAT:3 +DURATION:PT2M +END:VALARM +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-5@ubuntu-phablet +ACTION:AUDIO +TRIGGER;VALUE=DURATION;RELATED=START:-PT3H +REPEAT:3 +DURATION:PT2M +END:VALARM +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150405T213247Z-4371-32011-1698-6@ubuntu-phablet +ACTION:DISPLAY +DESCRIPTION:Go to the airport! +TRIGGER;VALUE=DURATION;RELATED=START:-PT3H +REPEAT:3 +DURATION:PT2M +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source b/tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source deleted file mode 100644 index 4b2f666..0000000 --- a/tests/test-eds-tasks-config-files/.config/evolution/sources/system-proxy.source +++ /dev/null @@ -1,21 +0,0 @@ - -[Data Source] -DisplayName=Default Proxy Settings -Enabled=true -Parent= - -[Proxy] -Method=default -IgnoreHosts=localhost;127.0.0.0/8;::1; -AutoconfigUrl= -FtpHost= -FtpPort=0 -HttpAuthPassword= -HttpAuthUser= -HttpHost= -HttpPort=8080 -HttpUseAuth=false -HttpsHost= -HttpsPort=0 -SocksHost= -SocksPort=0 diff --git a/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics b/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics deleted file mode 100644 index efde5f7..0000000 --- a/tests/test-eds-tasks-config-files/.local/share/evolution/tasks/system/tasks.ics +++ /dev/null @@ -1,28 +0,0 @@ -BEGIN:VCALENDAR -CALSCALE:GREGORIAN -PRODID:-//Ximian//NONSGML Evolution Calendar//EN -VERSION:2.0 -X-EVOLUTION-DATA-REVISION:2015-05-07T21:14:49.315443Z(0) -BEGIN:VTODO -UID:20150507T211449Z-4262-32011-1418-1@ubuntu-phablet -DTSTAMP:20150508T211449Z -DTSTART:20150508T164000 -RRULE:FREQ=WEEKLY;BYDAY=FR -SUMMARY:Alarm -CATEGORIES:x-canonical-alarm -CREATED:20150507T211449Z -LAST-MODIFIED:20150507T211449Z -BEGIN:VALARM -X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-2@ubuntu-phablet -ACTION:AUDIO -ATTACH:file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg -TRIGGER;VALUE=DURATION;RELATED=START:PT0S -END:VALARM -BEGIN:VALARM -X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-3@ubuntu-phablet -ACTION:DISPLAY -DESCRIPTION:Alarm -TRIGGER;VALUE=DURATION;RELATED=START:PT0S -END:VALARM -END:VTODO -END:VCALENDAR diff --git a/tests/test-eds-tasks.cpp b/tests/test-eds-tasks.cpp deleted file mode 100644 index 526dcc6..0000000 --- a/tests/test-eds-tasks.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2015 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - * - * Authors: - * Charles Kerr - */ - -#include - -#include -#include -#include -#include - -#include - -#include "glib-fixture.h" -#include "print-to.h" -#include "timezone-mock.h" -#include "wakeup-timer-mock.h" - -using namespace unity::indicator::datetime; -using VAlarmFixture = GlibFixture; - -/*** -**** -***/ - -TEST_F(VAlarmFixture, MultipleAppointments) -{ - // start the EDS engine - auto engine = std::make_shared(); - - // we need a consistent timezone for the planner and our local DateTimes - constexpr char const * zone_str {"America/Chicago"}; - auto tz = std::make_shared(zone_str); - auto gtz = g_time_zone_new(zone_str); - - // make a planner that looks at the first half of 2015 in EDS - auto planner = std::make_shared(engine, tz); - const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; - const DateTime range_end {gtz, 2015,6,31,23,59,59.5}; - planner->range().set(std::make_pair(range_begin, range_end)); - - // give EDS a moment to load - if (planner->appointments().get().empty()) { - g_message("waiting a moment for EDS to load..."); - auto on_appointments_changed = [this](const std::vector& appointments){ - g_message("ah, they loaded"); - if (!appointments.empty()) - g_main_loop_quit(loop); - }; - core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); - constexpr int max_wait_sec = 10; - wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); - } - - // what we expect to get... - Appointment expected_appt; - expected_appt.uid = "20150507T211449Z-4262-32011-1418-1@ubuntu-phablet"; - expected_appt.color = "#becedd"; - expected_appt.summary = "Alarm"; - std::array expected_alarms = { - Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5, 8,16,40,0)}), - Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,15,16,40,0)}), - Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,22,16,40,0)}), - Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,5,29,16,40,0)}), - Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6, 5,16,40,0)}), - Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,12,16,40,0)}), - Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,19,16,40,0)}), - Alarm({"Alarm", "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg", DateTime(gtz,2015,6,26,16,40,0)}) - }; - - // compare it to what we actually loaded... - const auto appts = planner->appointments().get(); - EXPECT_EQ(expected_alarms.size(), appts.size()); - for (size_t i=0, n=expected_alarms.size(); i. - * - * Authors: - * Charles Kerr - */ - -#include - -#include -#include -#include -#include - -#include - -#include "glib-fixture.h" -#include "print-to.h" -#include "timezone-mock.h" -#include "wakeup-timer-mock.h" - -using namespace unity::indicator::datetime; -using VAlarmFixture = GlibFixture; - -/*** -**** -***/ - -TEST_F(VAlarmFixture, MultipleAppointments) -{ - // start the EDS engine - auto engine = std::make_shared(); - - // we need a consistent timezone for the planner and our local DateTimes - constexpr char const * zone_str {"America/Chicago"}; - auto tz = std::make_shared(zone_str); - auto gtz = g_time_zone_new(zone_str); - - // make a planner that looks at the first half of 2015 in EDS - auto planner = std::make_shared(engine, tz); - const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; - const DateTime range_end {gtz, 2015,6,31,23,59,59.5}; - planner->range().set(std::make_pair(range_begin, range_end)); - - // give EDS a moment to load - if (planner->appointments().get().empty()) { - g_message("waiting a moment for EDS to load..."); - auto on_appointments_changed = [this](const std::vector& appointments){ - g_message("ah, they loaded"); - if (!appointments.empty()) - g_main_loop_quit(loop); - }; - core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); - constexpr int max_wait_sec = 10; - wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); - } - - // the planner should match what we've got in the calendar.ics file - const auto appts = planner->appointments().get(); - ASSERT_EQ(1, appts.size()); - const auto& appt = appts.front(); - ASSERT_EQ(8, appt.alarms.size()); - EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,35,0)}), appt.alarms[0]); - EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,37,0)}), appt.alarms[1]); - EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,39,0)}), appt.alarms[2]); - EXPECT_EQ(Alarm({"Time to pack!", "", DateTime(gtz,2015,4,23,13,41,0)}), appt.alarms[3]); - EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,35,0)}), appt.alarms[4]); - EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,37,0)}), appt.alarms[5]); - EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,39,0)}), appt.alarms[6]); - EXPECT_EQ(Alarm({"Go to the airport!", "", DateTime(gtz,2015,4,24,10,41,0)}), appt.alarms[7]); - - // now let's try this out with AlarmQueue... - // hook the planner up to a SimpleAlarmQueue and confirm that it triggers for each of the reminders - auto mock_clock = std::make_shared(range_begin); - std::shared_ptr clock = mock_clock; - std::shared_ptr wakeup_timer = std::make_shared(clock); - auto alarm_queue = std::make_shared(clock, planner, wakeup_timer); - int triggered_count = 0; - alarm_queue->alarm_reached().connect([&triggered_count, appt](const Appointment&, const Alarm& active_alarm) { - EXPECT_TRUE(std::find(appt.alarms.begin(), appt.alarms.end(), active_alarm) != appt.alarms.end()); - ++triggered_count; - }); - for (auto now=range_begin; nowset_localtime(now); - EXPECT_EQ(appt.alarms.size(), triggered_count); - - // cleanup - g_time_zone_unref(gtz); -} -- cgit v1.2.3 From cab736bed19f9a155766d32e77197339ff9e19a2 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 20 May 2015 18:24:40 -0500 Subject: re-enable the tests that I disabled earlier in this branch while focusing on the EDS bugfix --- tests/CMakeLists.txt | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d0b88b2..4530ff7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -45,21 +45,21 @@ function(add_test_by_name name) add_test (${TEST_NAME} ${TEST_NAME}) target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) endfunction() -#add_test_by_name(test-datetime) -#add_test_by_name(test-snap) -#add_test_by_name(test-actions) -#add_test_by_name(test-alarm-queue) -#add_test(NAME dear-reader-the-next-test-takes-60-seconds COMMAND true) -#add_test_by_name(test-clock) -#add_test_by_name(test-exporter) -#add_test_by_name(test-formatter) -#add_test_by_name(test-live-actions) -#add_test_by_name(test-locations) -#add_test_by_name(test-menus) -#add_test_by_name(test-planner) -#add_test_by_name(test-settings) -#add_test_by_name(test-timezone-file) -#add_test_by_name(test-utils) +add_test_by_name(test-datetime) +add_test_by_name(test-snap) +add_test_by_name(test-actions) +add_test_by_name(test-alarm-queue) +add_test(NAME dear-reader-the-next-test-takes-60-seconds COMMAND true) +add_test_by_name(test-clock) +add_test_by_name(test-exporter) +add_test_by_name(test-formatter) +add_test_by_name(test-live-actions) +add_test_by_name(test-locations) +add_test_by_name(test-menus) +add_test_by_name(test-planner) +add_test_by_name(test-settings) +add_test_by_name(test-timezone-file) +add_test_by_name(test-utils) set (TEST_NAME manual-test-snap) add_executable (${TEST_NAME} ${TEST_NAME}.cpp) -- cgit v1.2.3 From 44bd1414ea99b090657e50fd3fe48863a0d80d64 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 20 May 2015 18:56:15 -0500 Subject: remove detritus from the false starts. --- src/engine-eds.cpp | 396 +++++------------------------------------------------ 1 file changed, 34 insertions(+), 362 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 6302f06..456c80b 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -565,162 +565,6 @@ private: return out; } -#if 0 - static DateTime - to_datetime(struct icaltimetype& t, GTimeZone* gtz) - { - auto t_utc = icaltime_convert_to_zone(t, icaltimezone_get_utc_timezone()); - return DateTime(gtz, icaltime_as_timet(t_utc)); - } - - static struct icaltimetype - icaltimetype_from_datetime(const DateTime& dt, icaltimezone * default_timezone) - { - return icaltime_from_timet_with_zone(dt.to_unix(), false, default_timezone); - } - - static DateTime - get_trigger_time (icalcomponent* recurrence_instance, icalcomponent* valarm, GTimeZone* gtz) - { - DateTime ret; - - g_debug("%s getting trigger time for valarm %s", G_STRLOC, icalcomponent_as_ical_string(valarm)); - - auto trigger_property = icalcomponent_get_first_property(valarm, ICAL_TRIGGER_PROPERTY); - if (trigger_property == nullptr) - return ret; - - auto trigger = icalproperty_get_trigger(trigger_property); - g_debug("%s found a trigger", G_STRLOC); - g_debug("%s trigger time is %s", G_STRLOC, icaltime_as_ical_string(trigger.time)); - g_debug("%s trigger duration is %s", G_STRLOC, icaldurationtype_as_ical_string(trigger.duration)); - fflush(NULL); - - struct icaltimetype tt; - if (!icaltime_is_null_time(trigger.time)) - { - tt = trigger.time; - } - else - { - g_debug("%s got a NULL time, so it's relative to the component"); - // TRIGGER;RELATED=END:P5M 5 minutes after END - // TRIGGER;RELATED=START:-P15M 15 minutes before START - // get RELATED parameter - const char * related = icalproperty_get_parameter_as_string(trigger_property, "RELATED"); - if (related == nullptr) - related = "START"; // default value - g_debug("%s got a RELATED value of %s", G_STRLOC, related); - - struct icaltimetype reference_point; - if (!g_strcmp0(related, "START")) - reference_point = icalcomponent_get_dtstart(recurrence_instance); - else if (!g_strcmp0(related, "END")) - reference_point = icalcomponent_get_dtend(recurrence_instance); - else - reference_point = icaltime_null_time(); - - g_debug("%s reference point is %s", G_STRLOC, icaltime_as_ical_string(reference_point)); - tt = icaltime_add(reference_point, trigger.duration); - g_debug("%s reference point + offset is %s", G_STRLOC, icaltime_as_ical_string(tt)); - } - - if (icaltime_is_valid_time(tt)) - { - if (tt.zone == NULL) // floating time - ret = DateTime(gtz, tt.year, tt.month, tt.day, tt.hour, tt.minute, tt.second); - else // convert it to UTC for our time_t ctor - ret = DateTime(gtz, icaltime_as_timet(icaltime_convert_to_zone(tt, icaltimezone_get_utc_timezone()))); - } - - return ret; - } -#endif - -#if 0 -struct instance_data { - time_t start; - gboolean found; -}; - -static void -check_instance (icalcomponent *, - struct icaltime_span *span, - gpointer data) -{ - auto instance = static_cast(data); -auto start = icaltime_from_timet(span->start, false); -g_debug("%s span->start %s(%zu) instance->start %zu", G_STRLOC, icaltime_as_ical_string(start), (size_t)span->start, (size_t)instance->start); - - if (span->start == instance->start) - { -g_debug("%s FOUND!", G_STRLOC); - instance->found = TRUE; - } -} - -/** - * e_cal_util_construct_instance: - * @icalcomp: A recurring #icalcomponent - * @rid: The RECURRENCE-ID to construct a component for - * - * This checks that @rid indicates a valid recurrence of @icalcomp, and - * if so, generates a copy of @comp containing a RECURRENCE-ID of @rid. - * - * Returns: the instance, or %NULL. - **/ -static icalcomponent * -foo_cal_util_construct_instance (icalcomponent *icalcomp, - struct icaltimetype rid) -{ - struct instance_data instance; - struct icaltimetype start, end; - -g_debug("%s looking for instance at %s", G_STRLOC, icaltime_as_ical_string(rid)); -auto dtstart = icalcomponent_get_dtstart(icalcomp); -g_debug("%s in icalcomp whose dtstart is %s(%zu)", G_STRLOC, icaltime_as_ical_string(dtstart), (size_t)icaltime_as_timet(dtstart)); - - g_return_val_if_fail (icalcomp != NULL, NULL); - -g_debug("%s", G_STRLOC); - /* Make sure this is really recurring */ - if (!icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY) && - !icalcomponent_get_first_property (icalcomp, ICAL_RDATE_PROPERTY)) - return NULL; - -g_debug("%s", G_STRLOC); - /* Make sure the specified instance really exists */ - start = icaltime_convert_to_zone (rid, icaltimezone_get_utc_timezone ()); - instance.start = icaltime_as_timet(start); -g_debug("%s start %s (%zu)", G_STRLOC, icaltime_as_ical_string(start), instance.start); - end = start; - icaltime_adjust (&end, 0, 0, 0, 1); -g_debug("%s end %s", G_STRLOC, icaltime_as_ical_string(end)); - icaltime_adjust (&start, -30, 0, 0, 1); - icaltime_adjust (&end, 30, 0, 0, 1); -g_debug("%s start %s", G_STRLOC, icaltime_as_ical_string(start)); -g_debug("%s end %s", G_STRLOC, icaltime_as_ical_string(end)); - - //instance.start = icaltime_convert_to_zone (rid, icaltimezone_get_utc_timezone ()); - instance.found = FALSE; - icalcomponent_foreach_recurrence (icalcomp, start, end, - check_instance, &instance); - if (!instance.found) { -g_debug("%s not found", G_STRLOC); - return NULL; - } - - /* Make the instance */ - icalcomp = icalcomponent_new_clone (icalcomp); - icalcomponent_set_recurrenceid (icalcomp, rid); - - return icalcomp; -} -#endif - - - - static void add_alarms_to_subtask (ECalComponentAlarms * comp_alarms, AppointmentSubtask * subtask, @@ -760,8 +604,8 @@ g_debug("%s not found", G_STRLOC); return; /** - *** This looks like a component that we should notify users about. - *** Let's add it. + *** this looks like an appointment we can use... + *** populate reference_appointment from the component fields/props **/ Appointment reference_appointment; @@ -793,21 +637,41 @@ g_debug("%s not found", G_STRLOC); : reference_appointment.begin; e_cal_component_free_datetime(&eccdt_tmp); - g_debug("got appointment from %s to %s, status %d", + // get appointment.activation_url from x-props + auto icc = e_cal_component_get_icalcomponent(component); // icc owned by component + auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY); + while (icalprop != nullptr) { + const char * x_name = icalproperty_get_x_name(icalprop); + if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL)) { + const char * url = icalproperty_get_value_as_string(icalprop); + if ((url != nullptr) && reference_appointment.activation_url.empty()) + reference_appointment.activation_url = url; + } + icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY); + } + + g_debug("%s got appointment from %s to %s, status %d: %s", G_STRLOC, reference_appointment.begin.format("%F %T %z").c_str(), reference_appointment.end.format("%F %T %z").c_str(), - (int)status); + (int)status, + icalcomponent_as_ical_string(icc) /* string owned by ical */); - // build our Alarm map from the comp_alarms - auto icc = e_cal_component_get_icalcomponent(component); // icc owned by component - g_debug("%s icc as ical string: %s", G_STRLOC, icalcomponent_as_ical_string(icc)); - - /* outer map key is the component instance's start + end time; we build Appointment.begin and .end from that. - * inner map key is the alarm trigger, we build Alarm.time from that. - * inner map value is the Alarm. - * - * we use the inner map to fold together multiple valarms that occur at the same time; - * e.g. one valarm may have a display action and another an audio action + /** + *** Now loop through comp_alarms to get information that we need + *** to build the instance appointments and their alarms. + *** + *** Outer map key is the instance component's start + end time. + *** We build Appointment.begin and .end from that. + *** + *** inner map key is the alarm trigger, we build Alarm.time from that. + *** + *** inner map value is the Alarm. + *** + *** We map the alarms based on their trigger time so that we + *** can fold together multiple valarms that trigger for the + *** same componeng at the same time. This is commonplace; + *** e.g. one valarm will have a display action and another + *** will specify a sound to be played. */ std::map,std::map> alarms; for (auto l=comp_alarms->alarms; l!=nullptr; l=l->next) @@ -821,9 +685,6 @@ g_debug("%s not found", G_STRLOC); DateTime{gtz, ai->occur_end}); const auto trigger_time = DateTime{gtz, ai->trigger}; -g_message("%s occur_start %zu(%s)", G_STRLOC, (size_t)ai->occur_start, instance_time.first.format("%F %T %z").c_str()); -g_message("%s occur_end %zu(%s)", G_STRLOC, (size_t)ai->occur_end, instance_time.second.format("%F %T %z").c_str()); -g_message("%s trigger_time %zu(%s)", G_STRLOC, (size_t)ai->trigger, trigger_time.format("%F %T %z").c_str()); auto& alarm = alarms[instance_time][trigger_time]; if (alarm.text.empty()) @@ -836,7 +697,6 @@ g_message("%s trigger_time %zu(%s)", G_STRLOC, (size_t)ai->trigger, trigger_time e_cal_component_alarm_free(a); } - // now build the appointments for (auto& i : alarms) { Appointment appointment = reference_appointment; @@ -847,195 +707,7 @@ g_message("%s trigger_time %zu(%s)", G_STRLOC, (size_t)ai->trigger, trigger_time appointment.alarms.push_back(j.second); subtask->task->appointments.push_back(appointment); } - - // look for our recognized e-props - //if (icc != nullptr) - //{ -#if 0 - auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY); - while (icalprop) - { - const char * x_name = icalproperty_get_x_name(icalprop); - if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL)) - { - const char * url = icalproperty_get_value_as_string(icalprop); - if ((url != nullptr) && appointment.activation_url.empty()) - appointment.activation_url = url; - } - - icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY); - } - //} - - appointment.alarms.reserve(alarms.size()); - for (const auto& it : alarms) - appointment.alarms.push_back(it.second); - - subtask->task->appointments.push_back(appointment); -#endif - } - - -#if 0 - static gboolean - my_get_appointments_foreach(ECalComponent* component, - time_t begin, - time_t end, - gpointer gsubtask) - { - const auto vtype = e_cal_component_get_vtype(component); - auto subtask = static_cast(gsubtask); - - if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO)) - { - const gchar* uid = nullptr; - e_cal_component_get_uid(component, &uid); - - auto status = ICAL_STATUS_NONE; - e_cal_component_get_status(component, &status); - - // get the timezone we want to use for generated Appointments/Alarms - const char * location = icaltimezone_get_location(subtask->default_timezone); - auto gtz = g_time_zone_new(location); - g_debug("timezone abbreviation is %s", g_time_zone_get_abbreviation (gtz, 0)); - - const DateTime begin_dt { gtz, begin }; - const DateTime end_dt { gtz, end }; - g_debug ("got appointment from %s to %s, uid %s status %d", - begin_dt.format("%F %T %z").c_str(), - end_dt.format("%F %T %z").c_str(), - uid, - (int)status); - - // look for the in-house tags - bool disabled = false; - Appointment::Type type = Appointment::EVENT; - GSList * categ_list = nullptr; - e_cal_component_get_categories_list (component, &categ_list); - for (GSList * l=categ_list; l!=nullptr; l=l->next) { - auto tag = static_cast(l->data); - if (!g_strcmp0(tag, TAG_ALARM)) - type = Appointment::UBUNTU_ALARM; - if (!g_strcmp0(tag, TAG_DISABLED)) - disabled = true; - } - e_cal_component_free_categories_list(categ_list); - - if ((uid != nullptr) && - (!disabled) && - (status != ICAL_STATUS_COMPLETED) && - (status != ICAL_STATUS_CANCELLED)) - { - constexpr std::array omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 - Appointment appointment; - - ECalComponentText text {}; - e_cal_component_get_summary(component, &text); - if (text.value) - appointment.summary = text.value; - - auto icc = e_cal_component_get_icalcomponent(component); // component owns icc - if (icc) - { - g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak - - auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY); - while (icalprop) - { - const char * x_name = icalproperty_get_x_name(icalprop); - if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL)) - { - const char * url = icalproperty_get_value_as_string(icalprop); - if ((url != nullptr) && appointment.activation_url.empty()) - appointment.activation_url = url; - } - - icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY); - } - } - - appointment.begin = begin_dt; - appointment.end = end_dt; - appointment.color = subtask->color; - appointment.uid = uid; - appointment.type = type; - - auto e_alarms = e_cal_util_generate_alarms_for_comp(component, - subtask->begin, - subtask->end, - const_cast(omit.data()), - e_cal_client_resolve_tzid_cb, - subtask->client, - nullptr); - - std::map alarms; - - if (e_alarms != nullptr) - { - for (auto l=e_alarms->alarms; l!=nullptr; l=l->next) - { - auto ai = static_cast(l->data); - auto a = e_cal_component_get_alarm(component, ai->auid); - - if (a != nullptr) - { - const DateTime alarm_begin{gtz, ai->trigger}; - auto& alarm = alarms[alarm_begin]; - - if (alarm.text.empty()) - alarm.text = get_alarm_text(a); - if (alarm.audio_url.empty()) - alarm.audio_url = get_alarm_sound_url(a); - if (!alarm.time.is_set()) - alarm.time = alarm_begin; - - e_cal_component_alarm_free(a); - } - } - - e_cal_component_alarms_free(e_alarms); - } - // Hm, no alarm triggers? - // That's a bug in alarms created by some versions of ubuntu-ui-toolkit. - // If that's what's happening here, let's handle those alarms anyway - // by effectively injecting a TRIGGER;VALUE=DURATION;RELATED=START:PT0S - else if (appointment.is_ubuntu_alarm()) - { - Alarm tmp; - tmp.time = appointment.begin; - - auto auids = e_cal_component_get_alarm_uids(component); - for(auto l=auids; l!=nullptr; l=l->next) - { - const auto auid = static_cast(l->data); - auto a = e_cal_component_get_alarm(component, auid); - if (a != nullptr) - { - if (tmp.text.empty()) - tmp.text = get_alarm_text(a); - if (tmp.audio_url.empty()) - tmp.audio_url = get_alarm_sound_url(a); - e_cal_component_alarm_free(a); - } - } - cal_obj_uid_list_free(auids); - - alarms[tmp.time] = tmp; - } - - appointment.alarms.reserve(alarms.size()); - for (const auto& it : alarms) - appointment.alarms.push_back(it.second); - - subtask->task->appointments.push_back(appointment); - } - - g_time_zone_unref(gtz); - } - - return G_SOURCE_CONTINUE; } -#endif /*** **** -- cgit v1.2.3 From 70ccd1abfa7721393217336ba4048a2c51d76246 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 20 May 2015 20:22:52 -0500 Subject: copyediting --- src/engine-eds.cpp | 73 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 456c80b..d2b2fa0 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -503,28 +503,33 @@ private: GError * error = NULL; GSList * comps_slist = NULL; - if (e_cal_client_get_object_list_as_comps_finish (E_CAL_CLIENT(oclient), res, &comps_slist, &error)) + if (e_cal_client_get_object_list_as_comps_finish(E_CAL_CLIENT(oclient), + res, + &comps_slist, + &error)) { - // _generate_alarms takes a GList, so make a shallow copy of the comps GSList + // _generate_alarms takes a GList, so make a shallow one GList * comps_list = nullptr; for (auto l=comps_slist; l!=nullptr; l=l->next) comps_list = g_list_prepend(comps_list, l->data); - constexpr std::array omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 + constexpr std::array omit = { + (ECalComponentAlarmAction)-1 + }; // list of action types to omit, terminated with -1 GSList * comp_alarms = nullptr; auto subtask = static_cast(gsubtask); - e_cal_util_generate_alarms_for_list(comps_list, - subtask->begin.to_unix(), - subtask->end.to_unix(), - const_cast(omit.data()), - &comp_alarms, - e_cal_client_resolve_tzid_cb, - oclient, - subtask->default_timezone); + e_cal_util_generate_alarms_for_list( + comps_list, + subtask->begin.to_unix(), + subtask->end.to_unix(), + const_cast(omit.data()), + &comp_alarms, + e_cal_client_resolve_tzid_cb, + oclient, + subtask->default_timezone); // walk the alarms & add them - const char * location = icaltimezone_get_location(subtask->default_timezone); - auto gtz = g_time_zone_new(location); + auto gtz = g_time_zone_new(icaltimezone_get_location(subtask->default_timezone)); for (auto l=comp_alarms; l!=nullptr; l=l->next) add_alarms_to_subtask(static_cast(l->data), subtask, gtz); g_time_zone_unref(gtz); @@ -538,7 +543,7 @@ private: else if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning("indicator-datetime cannot mark one-time alarm as disabled: %s", error->message); + g_warning("can't get ecalcomponent list: %s", error->message); g_error_free(error); } @@ -578,13 +583,15 @@ private: // we only want calendar events and vtodos const auto vtype = e_cal_component_get_vtype(component); - if ((vtype != E_CAL_COMPONENT_EVENT) && (vtype != E_CAL_COMPONENT_TODO)) + if ((vtype != E_CAL_COMPONENT_EVENT) && + (vtype != E_CAL_COMPONENT_TODO)) return; // we're not interested in completed or cancelled components auto status = ICAL_STATUS_NONE; e_cal_component_get_status(component, &status); - if ((status == ICAL_STATUS_COMPLETED) || (status == ICAL_STATUS_CANCELLED)) + if ((status == ICAL_STATUS_COMPLETED) || + (status == ICAL_STATUS_CANCELLED)) return; // walk through the categories list to look for our in-house tags @@ -605,36 +612,36 @@ private: /** *** this looks like an appointment we can use... - *** populate reference_appointment from the component fields/props + *** populate baseline from the component fields/props **/ - Appointment reference_appointment; - reference_appointment.color = subtask->color; - reference_appointment.type = type; + Appointment baseline; + baseline.color = subtask->color; + baseline.type = type; // get appointment.uid const gchar* uid = nullptr; e_cal_component_get_uid(component, &uid); if (uid != nullptr) - reference_appointment.uid = uid; + baseline.uid = uid; // get appointment.summary ECalComponentText text {}; e_cal_component_get_summary(component, &text); if (text.value) - reference_appointment.summary = text.value; + baseline.summary = text.value; // get appointment.begin ECalComponentDateTime eccdt_tmp {}; e_cal_component_get_dtstart(component, &eccdt_tmp); - reference_appointment.begin = datetime_from_component_date_time(eccdt_tmp, gtz); + baseline.begin = datetime_from_component_date_time(eccdt_tmp, gtz); e_cal_component_free_datetime(&eccdt_tmp); // get appointment.end e_cal_component_get_dtend(component, &eccdt_tmp); - reference_appointment.end = eccdt_tmp.value != nullptr + baseline.end = eccdt_tmp.value != nullptr ? datetime_from_component_date_time(eccdt_tmp, gtz) - : reference_appointment.begin; + : baseline.begin; e_cal_component_free_datetime(&eccdt_tmp); // get appointment.activation_url from x-props @@ -644,15 +651,15 @@ private: const char * x_name = icalproperty_get_x_name(icalprop); if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL)) { const char * url = icalproperty_get_value_as_string(icalprop); - if ((url != nullptr) && reference_appointment.activation_url.empty()) - reference_appointment.activation_url = url; + if ((url != nullptr) && baseline.activation_url.empty()) + baseline.activation_url = url; } icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY); } g_debug("%s got appointment from %s to %s, status %d: %s", G_STRLOC, - reference_appointment.begin.format("%F %T %z").c_str(), - reference_appointment.end.format("%F %T %z").c_str(), + baseline.begin.format("%F %T %z").c_str(), + baseline.end.format("%F %T %z").c_str(), (int)status, icalcomponent_as_ical_string(icc) /* string owned by ical */); @@ -681,9 +688,9 @@ private: if (a == nullptr) continue; - const auto instance_time = std::make_pair(DateTime{gtz, ai->occur_start}, - DateTime{gtz, ai->occur_end}); - const auto trigger_time = DateTime{gtz, ai->trigger}; + auto instance_time = std::make_pair(DateTime{gtz, ai->occur_start}, + DateTime{gtz, ai->occur_end}); + auto trigger_time = DateTime{gtz, ai->trigger}; auto& alarm = alarms[instance_time][trigger_time]; @@ -699,7 +706,7 @@ private: for (auto& i : alarms) { - Appointment appointment = reference_appointment; + Appointment appointment = baseline; appointment.begin = i.first.first; appointment.end = i.first.second; appointment.alarms.reserve(i.second.size()); -- cgit v1.2.3 From 9e1f2db6694791beaf0f7340de2f523c95f2551b Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 20 May 2015 20:50:53 -0500 Subject: remove a little more leftover code from the false starts --- src/engine-eds.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index d2b2fa0..563d9a3 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -139,7 +139,6 @@ public: auto end_str = isodate_from_time_t(end_timet); auto sexp = g_strdup_printf("(has-alarms-in-range? (make-time \"%s\") (make-time \"%s\"))", begin_str, end_str); g_debug("%s sexp is %s", G_STRLOC, sexp); - //e_cal_client_get_object_list(client, e_cal_client_get_object_list_as_comps(client, sexp, m_cancellable, @@ -425,7 +424,6 @@ private: ECalClient* client; std::string color; icaltimezone* default_timezone; - GTimeZone * gtz; DateTime begin; DateTime end; @@ -438,18 +436,12 @@ private: task(task_in), client(client_in), default_timezone(default_tz), - gtz(g_time_zone_new(icaltimezone_get_location(default_tz))), begin(begin_), end(end_) { if (color_in) color = color_in; } - - ~AppointmentSubtask() - { - g_clear_pointer(>z, g_time_zone_unref); - } }; static std::string get_alarm_text(ECalComponentAlarm * alarm) -- cgit v1.2.3 From 105abe6c85d080f369c246e21d4d19c75ffb18c3 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 21 May 2015 05:58:00 -0500 Subject: in engine-eds.cpp's get_appointments(), clear the begin_str and end_str variables as soon as we're done with them. --- src/engine-eds.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 563d9a3..37f72e5 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -135,17 +135,19 @@ public: const auto begin_timet = begin.to_unix(); const auto end_timet = end.to_unix(); + auto begin_str = isodate_from_time_t(begin_timet); auto end_str = isodate_from_time_t(end_timet); auto sexp = g_strdup_printf("(has-alarms-in-range? (make-time \"%s\") (make-time \"%s\"))", begin_str, end_str); + g_clear_pointer(&begin_str, g_free); + g_clear_pointer(&end_str, g_free); + g_debug("%s sexp is %s", G_STRLOC, sexp); e_cal_client_get_object_list_as_comps(client, sexp, m_cancellable, on_object_list_ready, subtask); - g_free(begin_str); - g_free(end_str); g_free(sexp); } } -- cgit v1.2.3 From 505d4fae96e1d6849f119e9b31588f90073a4102 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 21 May 2015 06:01:01 -0500 Subject: in engine-eds.cpp's on_object_list_ready(), always mark the subtask as finished even if the dbus call failed. --- src/engine-eds.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 37f72e5..8350e77 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -496,6 +496,7 @@ private: { GError * error = NULL; GSList * comps_slist = NULL; + auto subtask = static_cast(gsubtask); if (e_cal_client_get_object_list_as_comps_finish(E_CAL_CLIENT(oclient), res, @@ -511,7 +512,6 @@ private: (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 GSList * comp_alarms = nullptr; - auto subtask = static_cast(gsubtask); e_cal_util_generate_alarms_for_list( comps_list, subtask->begin.to_unix(), @@ -532,7 +532,6 @@ private: e_cal_free_alarms(comp_alarms); g_list_free(comps_list); e_cal_client_free_ecalcomp_slist(comps_slist); - delete subtask; } else if (error != nullptr) { @@ -541,6 +540,8 @@ private: g_error_free(error); } + + delete subtask; } static DateTime -- cgit v1.2.3 From 1b2b949e449ed961624828e7d13bbf53f56abda8 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 21 May 2015 06:09:52 -0500 Subject: in run-eds-ics-test.sh, slightly cleaner ics file copying --- tests/run-eds-ics-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run-eds-ics-test.sh b/tests/run-eds-ics-test.sh index 0eeafad..a257de4 100755 --- a/tests/run-eds-ics-test.sh +++ b/tests/run-eds-ics-test.sh @@ -48,7 +48,7 @@ fi # if there's a specific ics file to test, copy it on top of the canned confilg files if [ -e $9 ]; then echo "copying $9 into $HOME" - cp --verbose --archive $9 $HOME/.local/share/evolution/tasks/system/tasks.ics + cp --verbose --archive $9 ${XDG_DATA_HOME}/evolution/tasks/system/tasks.ics fi # run dbus-test-runner -- cgit v1.2.3 From 58efd72df90047765721c6e931da22abdb3a956a Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 21 May 2015 07:47:24 -0500 Subject: in eds-engine, add a occur-in-time-range sexp to handle events that are interesting but don't require user notification alarms --- src/engine-eds.cpp | 248 ++++++++++++++++++++++------------ tests/CMakeLists.txt | 1 + tests/print-to.h | 11 +- tests/test-eds-ics-all-day-events.cpp | 91 +++++++++++++ tests/test-eds-ics-all-day-events.ics | 19 +++ 5 files changed, 284 insertions(+), 86 deletions(-) create mode 100644 tests/test-eds-ics-all-day-events.cpp create mode 100644 tests/test-eds-ics-all-day-events.ics diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 8350e77..b171fda 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -103,52 +103,51 @@ public: *** walk through the sources to build the appointment list **/ - auto task_deleter = [](Task* task){ - // give the caller the (sorted) finished product - auto& a = task->appointments; - std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;}); - task->func(a); - // we're done; delete the task - g_debug("time to delete task %p", (void*)task); - delete task; - }; - - std::shared_ptr main_task(new Task(this, func), task_deleter); + auto gtz = default_timezone != nullptr + ? g_time_zone_new(icaltimezone_get_location(default_timezone)) + : g_time_zone_new_local(); + auto main_task = std::make_shared(this, func, default_timezone, gtz, begin, end); for (auto& kv : m_clients) { auto& client = kv.second; if (default_timezone != nullptr) e_cal_client_set_default_timezone(client, default_timezone); + g_debug("calling e_cal_client_generate_instances for %p", (void*)client); - // start a new subtask to enumerate all the components in this client. auto& source = kv.first; auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR); const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension)); - g_debug("calling e_cal_client_generate_instances for %p", (void*)client); - auto subtask = new AppointmentSubtask(main_task, - client, - color, - default_timezone, - begin, - end); - - const auto begin_timet = begin.to_unix(); - const auto end_timet = end.to_unix(); - - auto begin_str = isodate_from_time_t(begin_timet); - auto end_str = isodate_from_time_t(end_timet); - auto sexp = g_strdup_printf("(has-alarms-in-range? (make-time \"%s\") (make-time \"%s\"))", begin_str, end_str); + + auto begin_str = isodate_from_time_t(begin.to_unix()); + auto end_str = isodate_from_time_t(end.to_unix()); + auto sexp_fmt = g_strdup_printf("(%%s? (make-time \"%s\") (make-time \"%s\"))", begin_str, end_str); g_clear_pointer(&begin_str, g_free); g_clear_pointer(&end_str, g_free); - g_debug("%s sexp is %s", G_STRLOC, sexp); - e_cal_client_get_object_list_as_comps(client, - sexp, - m_cancellable, - on_object_list_ready, - subtask); - g_free(sexp); + // ask EDS about alarms that occur in this window... + auto sexp = g_strdup_printf(sexp_fmt, "has-alarms-in-range"); + g_debug("%s alarm sexp is %s", G_STRLOC, sexp); + e_cal_client_get_object_list_as_comps( + client, + sexp, + m_cancellable, + on_alarm_component_list_ready, + new ClientSubtask(main_task, client, color)); + g_clear_pointer(&sexp, g_free); + + // ask EDS about events that occur in this window... + sexp = g_strdup_printf(sexp_fmt, "occur-in-time-range"); + g_debug("%s event sexp is %s", G_STRLOC, sexp); + e_cal_client_get_object_list_as_comps( + client, + sexp, + m_cancellable, + on_event_component_list_ready, + new ClientSubtask(main_task, client, color)); + g_clear_pointer(&sexp, g_free); + + g_clear_pointer(&sexp_fmt, g_free); } } @@ -416,30 +415,45 @@ private: { Impl* p; appointment_func func; + icaltimezone* default_timezone; // pointer owned by libical + GTimeZone* gtz; std::vector appointments; - Task(Impl* p_in, const appointment_func& func_in): p(p_in), func(func_in) {} + const DateTime begin; + const DateTime end; + + Task(Impl* p_in, + appointment_func func_in, + icaltimezone* tz_in, + GTimeZone* gtz_in, + const DateTime& begin_in, + const DateTime& end_in): + p{p_in}, + func{func_in}, + default_timezone{tz_in}, + gtz{gtz_in}, + begin{begin_in}, + end{end_in} {} + + ~Task() { + g_clear_pointer(>z, g_time_zone_unref); + // give the caller the sorted finished product + auto& a = appointments; + std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;}); + func(a); + }; }; - struct AppointmentSubtask + struct ClientSubtask { std::shared_ptr task; ECalClient* client; std::string color; - icaltimezone* default_timezone; - DateTime begin; - DateTime end; - - AppointmentSubtask(const std::shared_ptr& task_in, - ECalClient* client_in, - const char* color_in, - icaltimezone* default_tz, - DateTime begin_, - DateTime end_): + + ClientSubtask(const std::shared_ptr& task_in, + ECalClient* client_in, + const char* color_in): task(task_in), - client(client_in), - default_timezone(default_tz), - begin(begin_), - end(end_) + client(client_in) { if (color_in) color = color_in; @@ -490,13 +504,13 @@ private: } static void - on_object_list_ready(GObject * oclient, - GAsyncResult * res, - gpointer gsubtask) + on_alarm_component_list_ready(GObject * oclient, + GAsyncResult * res, + gpointer gsubtask) { GError * error = NULL; GSList * comps_slist = NULL; - auto subtask = static_cast(gsubtask); + auto subtask = static_cast(gsubtask); if (e_cal_client_get_object_list_as_comps_finish(E_CAL_CLIENT(oclient), res, @@ -514,19 +528,17 @@ private: GSList * comp_alarms = nullptr; e_cal_util_generate_alarms_for_list( comps_list, - subtask->begin.to_unix(), - subtask->end.to_unix(), + subtask->task->begin.to_unix(), + subtask->task->end.to_unix(), const_cast(omit.data()), &comp_alarms, e_cal_client_resolve_tzid_cb, oclient, - subtask->default_timezone); + subtask->task->default_timezone); // walk the alarms & add them - auto gtz = g_time_zone_new(icaltimezone_get_location(subtask->default_timezone)); for (auto l=comp_alarms; l!=nullptr; l=l->next) - add_alarms_to_subtask(static_cast(l->data), subtask, gtz); - g_time_zone_unref(gtz); + add_alarms_to_subtask(static_cast(l->data), subtask, subtask->task->gtz); // cleanup e_cal_free_alarms(comp_alarms); @@ -544,6 +556,36 @@ private: delete subtask; } + static void + on_event_component_list_ready(GObject * oclient, + GAsyncResult * res, + gpointer gsubtask) + { + GError * error = NULL; + GSList * comps_slist = NULL; + auto subtask = static_cast(gsubtask); + + if (e_cal_client_get_object_list_as_comps_finish(E_CAL_CLIENT(oclient), + res, + &comps_slist, + &error)) + { + for (auto l=comps_slist; l!=nullptr; l=l->next) + add_event_to_subtask(static_cast(l->data), subtask, subtask->task->gtz); + + e_cal_client_free_ecalcomp_slist(comps_slist); + } + else if (error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("can't get ecalcomponent list: %s", error->message); + + g_error_free(error); + } + + delete subtask; + } + static DateTime datetime_from_component_date_time(const ECalComponentDateTime & in, GTimeZone * default_timezone) @@ -565,54 +607,42 @@ private: return out; } - static void - add_alarms_to_subtask (ECalComponentAlarms * comp_alarms, - AppointmentSubtask * subtask, - GTimeZone * gtz) + static bool + is_component_interesting(ECalComponent * component) { - auto& component = comp_alarms->comp; - - /** - *** Filter out components that we're not interested in - **/ - // we only want calendar events and vtodos const auto vtype = e_cal_component_get_vtype(component); if ((vtype != E_CAL_COMPONENT_EVENT) && (vtype != E_CAL_COMPONENT_TODO)) - return; + return false; // we're not interested in completed or cancelled components auto status = ICAL_STATUS_NONE; e_cal_component_get_status(component, &status); if ((status == ICAL_STATUS_COMPLETED) || (status == ICAL_STATUS_CANCELLED)) - return; + return false; - // walk through the categories list to look for our in-house tags + // we don't want disabled alarms bool disabled = false; - Appointment::Type type = Appointment::EVENT; GSList * categ_list = nullptr; e_cal_component_get_categories_list (component, &categ_list); for (GSList * l=categ_list; l!=nullptr; l=l->next) { auto tag = static_cast(l->data); - if (!g_strcmp0(tag, TAG_ALARM)) - type = Appointment::UBUNTU_ALARM; if (!g_strcmp0(tag, TAG_DISABLED)) disabled = true; } e_cal_component_free_categories_list(categ_list); - if (disabled) // don't notify users about disabled alarms... - return; + if (disabled) + return false; - /** - *** this looks like an appointment we can use... - *** populate baseline from the component fields/props - **/ + return true; + } + static Appointment + get_appointment(ECalComponent * component, GTimeZone * gtz) + { Appointment baseline; - baseline.color = subtask->color; - baseline.type = type; // get appointment.uid const gchar* uid = nullptr; @@ -652,12 +682,60 @@ private: icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY); } - g_debug("%s got appointment from %s to %s, status %d: %s", G_STRLOC, + // get appointment.type + baseline.type = Appointment::EVENT; + GSList * categ_list = nullptr; + e_cal_component_get_categories_list (component, &categ_list); + for (GSList * l=categ_list; l!=nullptr; l=l->next) { + auto tag = static_cast(l->data); + if (!g_strcmp0(tag, TAG_ALARM)) + baseline.type = Appointment::UBUNTU_ALARM; + } + e_cal_component_free_categories_list(categ_list); + + g_debug("%s got appointment from %s to %s: %s", G_STRLOC, baseline.begin.format("%F %T %z").c_str(), baseline.end.format("%F %T %z").c_str(), - (int)status, icalcomponent_as_ical_string(icc) /* string owned by ical */); + return baseline; + } + + static void + add_event_to_subtask(ECalComponent * component, + ClientSubtask * subtask, + GTimeZone * gtz) + { + // events with alarms are covered by add_alarm_to_subtask(), + // so skip them here + auto auids = e_cal_component_get_alarm_uids(component); + const bool has_alarms = auids != nullptr; + cal_obj_uid_list_free(auids); + if (has_alarms) + return; + + // add it. simple, eh? + if (is_component_interesting(component)) + { + Appointment appointment = get_appointment(component, gtz); + appointment.color = subtask->color; + subtask->task->appointments.push_back(appointment); + } + } + + static void + add_alarms_to_subtask(ECalComponentAlarms * comp_alarms, + ClientSubtask * subtask, + GTimeZone * gtz) + { + auto& component = comp_alarms->comp; + + if (!is_component_interesting(component)) + return; + + Appointment baseline = get_appointment(component, gtz); + baseline.color = subtask->color; + /** *** Now loop through comp_alarms to get information that we need *** to build the instance appointments and their alarms. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4530ff7..1e22100 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -94,6 +94,7 @@ function(add_eds_ics_test_by_name name) ${CMAKE_CURRENT_SOURCE_DIR}/test-eds-ics-config-files # arg8: base directory for config file template ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}.ics) # arg9: the ical file for this test endfunction() +add_eds_ics_test_by_name(test-eds-ics-all-day-events) add_eds_ics_test_by_name(test-eds-ics-repeating-events) add_eds_ics_test_by_name(test-eds-ics-nonrepeating-events) add_eds_ics_test_by_name(test-eds-ics-repeating-valarms) diff --git a/tests/print-to.h b/tests/print-to.h index 78cf574..f4af2f7 100644 --- a/tests/print-to.h +++ b/tests/print-to.h @@ -32,10 +32,19 @@ namespace datetime { **** PrintTo() functions for GTest to represent objects as strings ***/ +void +PrintTo(const DateTime& datetime, std::ostream* os) +{ + *os << "{time:'" << datetime.format("%F %T %z") << '}'; +} + void PrintTo(const Alarm& alarm, std::ostream* os) { - *os << "{text:'" << alarm.text << "', audio_url:'" << alarm.audio_url << "', time:'"<. + * + * Authors: + * Charles Kerr + */ + +#include + +#include +#include +#include +#include + +#include + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace unity::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, MultipleAppointments) +{ + // start the EDS engine + auto engine = std::make_shared(); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Chicago"}; + auto tz = std::make_shared(zone_str); + auto gtz = g_time_zone_new(zone_str); + + // make a planner that looks at the first half of 2015 in EDS + auto planner = std::make_shared(engine, tz); + const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2015,6,31,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // what we expect to get... + Appointment expected_appt; + expected_appt.uid = "20150521T111538Z-7449-1000-3572-0@ghidorah"; + expected_appt.color = "#becedd"; + expected_appt.summary = "Memorial Day"; + expected_appt.begin = DateTime{gtz,2015,5,25,0,0,0}; + expected_appt.end = DateTime{gtz,2015,5,26,0,0,0}; + + // compare it to what we actually loaded... + const auto appts = planner->appointments().get(); + ASSERT_EQ(1, appts.size()); + const auto& appt = appts[0]; + EXPECT_EQ(expected_appt.begin, appt.begin); + EXPECT_EQ(expected_appt.end, appt.end); + EXPECT_EQ(expected_appt.uid, appt.uid); + EXPECT_EQ(expected_appt.color, appt.color); + EXPECT_EQ(expected_appt.summary, appt.summary); + EXPECT_EQ(0, appt.alarms.size()); + + // cleanup + g_time_zone_unref(gtz); +} diff --git a/tests/test-eds-ics-all-day-events.ics b/tests/test-eds-ics-all-day-events.ics new file mode 100644 index 0000000..0cf7dab --- /dev/null +++ b/tests/test-eds-ics-all-day-events.ics @@ -0,0 +1,19 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2015-05-07T21:14:49.315443Z(0) +BEGIN:VEVENT +UID:20150521T111538Z-7449-1000-3572-0@ghidorah +DTSTAMP:20150521T001128Z +DTSTART;VALUE=DATE:20150525 +DTEND;VALUE=DATE:20150526 +TRANSP:TRANSPARENT +SEQUENCE:2 +SUMMARY:Memorial Day +DESCRIPTION:Today is Memorial Day +CLASS:PUBLIC +CREATED:20150521T111638Z +LAST-MODIFIED:20150521T111638Z +END:VEVENT +END:VCALENDAR -- cgit v1.2.3 From 3c85ae61b63c484d8465468c07860fc332ca5072 Mon Sep 17 00:00:00 2001 From: CI Train Bot Date: Thu, 21 May 2015 15:24:31 +0000 Subject: Releasing 13.10.0+15.04.20150521-0ubuntu1 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index 33382d3..385a391 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +indicator-datetime (13.10.0+15.04.20150521-0ubuntu1) vivid; urgency=medium + + [ Charles Kerr ] + * Fix regression that caused nonrepeating alarms to go off at the + wrong time. (LP: #1456281) + + -- CI Train Bot Thu, 21 May 2015 15:24:31 +0000 + indicator-datetime (13.10.0+15.04.20150515-0ubuntu1) vivid; urgency=medium [ Charles Kerr ] -- cgit v1.2.3 From 25c99926957688340d794a92f4b47632943f6901 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 17 Jun 2015 23:52:52 -0500 Subject: add (failing) regression test for x-canonical-alarm components whose valarms have no triggers --- tests/CMakeLists.txt | 1 + tests/print-to.h | 24 +++++++ tests/test-eds-ics-missing-trigger.cpp | 116 +++++++++++++++++++++++++++++++++ tests/test-eds-ics-missing-trigger.ics | 45 +++++++++++++ 4 files changed, 186 insertions(+) create mode 100644 tests/test-eds-ics-missing-trigger.cpp create mode 100644 tests/test-eds-ics-missing-trigger.ics diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1e22100..123aa9a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -98,6 +98,7 @@ add_eds_ics_test_by_name(test-eds-ics-all-day-events) add_eds_ics_test_by_name(test-eds-ics-repeating-events) add_eds_ics_test_by_name(test-eds-ics-nonrepeating-events) add_eds_ics_test_by_name(test-eds-ics-repeating-valarms) +add_eds_ics_test_by_name(test-eds-ics-missing-trigger) # disabling the timezone unit tests because they require diff --git a/tests/print-to.h b/tests/print-to.h index f4af2f7..357925f 100644 --- a/tests/print-to.h +++ b/tests/print-to.h @@ -47,6 +47,30 @@ PrintTo(const Alarm& alarm, std::ostream* os) *os << '}'; } +void +PrintTo(const Appointment& appointment, std::ostream* os) +{ + *os << '{'; + + *os << "{uid:'" << appointment.uid << "'}" + << "{color:'" << appointment.color << "'}" + << "{summary:'" << appointment.summary << "'}" + << "{activation_url:'" << appointment.activation_url << "'}"; + + *os << "{begin:"; + PrintTo(appointment.begin, os); + *os << '}'; + + *os << "{end:"; + PrintTo(appointment.end, os); + *os << '}'; + + for(const auto& alarm : appointment.alarms) + PrintTo(alarm, os); + + *os << '}'; +} + } // namespace datetime } // namespace indicator } // namespace unity diff --git a/tests/test-eds-ics-missing-trigger.cpp b/tests/test-eds-ics-missing-trigger.cpp new file mode 100644 index 0000000..ba87011 --- /dev/null +++ b/tests/test-eds-ics-missing-trigger.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2015 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include +#include +#include +#include + +#include + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace unity::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, MissingTriggers) +{ + // start the EDS engine + auto engine = std::make_shared(); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Chicago"}; + auto tz = std::make_shared(zone_str); + auto gtz = g_time_zone_new(zone_str); + + // make a planner that looks at the first half of 2015 in EDS + auto planner = std::make_shared(engine, tz); + const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2015,6,31,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // build expected: one-time alarm + std::vector expected; + Appointment a; + a.type = Appointment::UBUNTU_ALARM; + a.uid = "20150617T211838Z-6217-32011-2036-1@ubuntu-phablet"; + a.color = "#becedd"; + a.summary = "One Time Alarm"; + a.begin = DateTime { gtz, 2015, 6, 18, 10, 0, 0}; + a.end = a.begin; + a.alarms.resize(1); + a.alarms[0].audio_url = "file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"; + a.alarms[0].time = a.begin; + a.alarms[0].text = a.summary; + expected.push_back(a); + + // build expected: recurring alarm + a.uid = "20150617T211913Z-6217-32011-2036-5@ubuntu-phablet"; + a.summary = "Recurring Alarm"; + a.alarms[0].text = a.summary; + std::array recurrences { + DateTime{ gtz, 2015, 6, 18, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 19, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 20, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 21, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 22, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 23, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 24, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 25, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 26, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 27, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 28, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 29, 10, 1, 0 }, + DateTime{ gtz, 2015, 6, 30, 10, 1, 0 }, + DateTime{ gtz, 2015, 7, 1, 10, 1, 0 } + }; + for (const auto& time : recurrences) { + a.begin = a.end = a.alarms[0].time = time; + expected.push_back(a); + } + + // the planner should match what we've got in the calendar.ics file + const auto appts = planner->appointments().get(); + EXPECT_EQ(expected, appts); + + // cleanup + g_time_zone_unref(gtz); +} diff --git a/tests/test-eds-ics-missing-trigger.ics b/tests/test-eds-ics-missing-trigger.ics new file mode 100644 index 0000000..048e7f2 --- /dev/null +++ b/tests/test-eds-ics-missing-trigger.ics @@ -0,0 +1,45 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2015-06-17T21:19:13.980613Z(3) +BEGIN:VTODO +UID:20150617T211838Z-6217-32011-2036-1@ubuntu-phablet +DTSTAMP:20150617T211838Z +DTSTART:20150618T100000 +SUMMARY:One Time Alarm +CATEGORIES:x-canonical-alarm +CREATED:20150617T211838Z +LAST-MODIFIED:20150617T211838Z +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150617T211838Z-6217-32011-2036-2@ubuntu-phablet +ACTION:AUDIO +ATTACH:file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg +END:VALARM +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150617T211838Z-6217-32011-2036-3@ubuntu-phablet +ACTION:DISPLAY +DESCRIPTION:One Time Alarm +END:VALARM +END:VTODO +BEGIN:VTODO +UID:20150617T211913Z-6217-32011-2036-5@ubuntu-phablet +DTSTAMP:20150617T211913Z +DTSTART:20150618T100100 +RRULE:FREQ=DAILY +SUMMARY:Recurring Alarm +CATEGORIES:x-canonical-alarm +CREATED:20150617T211913Z +LAST-MODIFIED:20150617T211913Z +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150617T211913Z-6217-32011-2036-6@ubuntu-phablet +ACTION:AUDIO +ATTACH:file:///usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg +END:VALARM +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150617T211913Z-6217-32011-2036-7@ubuntu-phablet +ACTION:DISPLAY +DESCRIPTION:Recurring Alarm +END:VALARM +END:VTODO +END:VCALENDAR -- cgit v1.2.3 From 23dc0fd91060fdd2f0d26a7b5bf22a952f08301f Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 17 Jun 2015 23:53:52 -0500 Subject: if older clock-app alarms don't have triggers, add them. --- src/engine-eds.cpp | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index b171fda..b6b4f5e 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -297,10 +297,14 @@ private: // add the client to our collection auto self = static_cast(gself); g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client))); - self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client); + auto source = e_client_get_source(client); + auto ecc = E_CAL_CLIENT(client); + self->m_clients[source] = ecc; + + self->ensure_client_alarms_have_triggers(ecc); // now create a view for it so that we can listen for changes - e_cal_client_get_view (E_CAL_CLIENT(client), + e_cal_client_get_view (ecc, "#t", // match all self->m_cancellable, on_client_view_ready, @@ -409,6 +413,135 @@ private: static_cast(gself)->set_dirty_soon(); } + /*** + **** + ***/ + + // old ubuntu-clock-app alarms created VTODO VALARMS without the + // required 'TRIGGER' property... http://pad.lv/1465806 + + void ensure_client_alarms_have_triggers(ECalClient* client) + { + // ask the EDS server for all the ubuntu-clock-app alarms... + + auto sexp = g_strdup_printf("has-categories? '%s'", TAG_ALARM); + + e_cal_client_get_object_list_as_comps( + client, + sexp, + m_cancellable, + ensure_client_alarms_have_triggers_async_cb, + this); + + g_clear_pointer(&sexp, g_free); + } + + static void ensure_client_alarms_have_triggers_async_cb( + GObject * oclient, + GAsyncResult * res, + gpointer gself) + { + ECalClient * client = E_CAL_CLIENT(oclient); + GError * error = nullptr; + GSList * components = nullptr; + + if (e_cal_client_get_object_list_as_comps_finish(client, + res, + &components, + &error)) + { + auto self = static_cast(gself); + self->ensure_canonical_alarms_have_triggers(client, components); + e_cal_client_free_ecalcomp_slist(components); + } + else if (error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("can't get clock-app alarm list: %s", error->message); + + g_error_free(error); + } + } + + void ensure_canonical_alarms_have_triggers(ECalClient * client, + GSList * components) + { + GSList * modify_slist = nullptr; + + // for each component.. + for (auto l=components; l!=nullptr; l=l->next) + { + bool changed = false; + + // for each alarm... + auto component = E_CAL_COMPONENT(l->data); + auto auids = e_cal_component_get_alarm_uids(component); + for(auto l=auids; l!=nullptr; l=l->next) + { + auto auid = static_cast(l->data); + auto alarm = e_cal_component_get_alarm(component, auid); + if (alarm == nullptr) + continue; + + // if the alarm has no trigger, add one. + ECalComponentAlarmTrigger trigger; + e_cal_component_alarm_get_trigger(alarm, &trigger); + if (trigger.type == E_CAL_COMPONENT_ALARM_TRIGGER_NONE) + { + trigger.type = E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START; + trigger.u.rel_duration = icaldurationtype_from_int(0); + e_cal_component_alarm_set_trigger (alarm, trigger); + changed = true; + } + + g_clear_pointer(&alarm, e_cal_component_alarm_free); + } + g_clear_pointer(&auids, cal_obj_uid_list_free); + + if (changed) + { + auto icc = e_cal_component_get_icalcomponent(component); // icc owned by ecc + modify_slist = g_slist_prepend(modify_slist, icc); + } + } + + if (modify_slist != nullptr) + { + e_cal_client_modify_objects(client, + modify_slist, + E_CAL_OBJ_MOD_ALL, + m_cancellable, + ensure_canonical_alarms_have_triggers_async_cb, + this); + + g_clear_pointer(&modify_slist, g_slist_free); + } + } + + // log a warning if e_cal_client_modify_objects() failed + static void ensure_canonical_alarms_have_triggers_async_cb( + GObject * oclient, + GAsyncResult * res, + gpointer /*gself*/) + { + GError * error = nullptr; + + e_cal_client_modify_objects_finish (E_CAL_CLIENT(oclient), res, &error); + + if (error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("couldn't add alarm triggers: %s", error->message); + + g_error_free(error); + } + } + + /*** + **** + ***/ + + typedef std::function&)> appointment_func; struct Task -- cgit v1.2.3 From cfefa6fbb0e0e552e31dc14b06977429b1fcf3ca Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 22 Jun 2015 09:17:48 -0500 Subject: don't copy debian/changelog from vivid --- debian/changelog | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/debian/changelog b/debian/changelog index ecf65a8..811e854 100644 --- a/debian/changelog +++ b/debian/changelog @@ -6,33 +6,6 @@ indicator-datetime (13.10.0+15.10.20150515-0ubuntu1) wily; urgency=medium -- CI Train Bot Fri, 15 May 2015 16:41:41 +0000 -indicator-datetime (13.10.0+15.04.20150521-0ubuntu1) vivid; urgency=medium - - [ Charles Kerr ] - * Fix regression that caused nonrepeating alarms to go off at the - wrong time. (LP: #1456281) - - -- CI Train Bot Thu, 21 May 2015 15:24:31 +0000 - -indicator-datetime (13.10.0+15.04.20150515-0ubuntu1) vivid; urgency=medium - - [ Charles Kerr ] - * Honor the 'other vibrations' setting to enable/disable vibrations - when alarm notifications are shown. - - -- CI Train Bot Fri, 15 May 2015 19:33:54 +0000 - -indicator-datetime (13.10.0+15.04.20150508.2-0ubuntu1) vivid; urgency=medium - - [ CI Train Bot ] - * New rebuild forced. - - [ Charles Kerr ] - * If the "X-CANONICAL-ACTIVATION-URL" x-prop is defined in the VTODO - or VEVENT, use it for url delegation. (LP: #1426519) - - -- CI Train Bot Fri, 08 May 2015 20:01:09 +0000 - indicator-datetime (13.10.0+15.04.20150406-0ubuntu1) vivid; urgency=medium [ Charles Kerr ] -- cgit v1.2.3