From 3369975ea949c33c3eeb4a4668d5af402cb8781a Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Mon, 25 Mar 2024 10:58:29 -0700 Subject: [PATCH] Completed time range search of recurring events. --- webcit-ng/README.md | 14 +++---- webcit-ng/server/caldav_reports.c | 65 ++++++++++++++++++++++--------- webcit/calendar.c | 4 +- 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/webcit-ng/README.md b/webcit-ng/README.md index 95ab1f80e..bcae8a78b 100644 --- a/webcit-ng/README.md +++ b/webcit-ng/README.md @@ -4,16 +4,17 @@ This is WebCit-NG, a complete refactoring of the WebCit server that will focus on "REST first" and build around that. The server code is well layered with as little spaghetti as possible. -Please don't mess with this yet. I'm only pushing it upstream so it gets backed up. +This is a work in progress and not ready for production use. It is not feature +complete. ## Design goals * Hold as little state as possible * Require NO cleanup. Killing the process lets the OS reclaim all resources. -* As much as possible, resources should be freed by just coming back down the stack. - Avoid global variables and thread-local variables as much as possible. +* As much as possible, resources should be freed by just coming back down the + stack. Avoid global variables and thread-local variables as much as possible. * Readability of the code is more important than shaving off a few CPU cycles. -* Throw sensitive data such as passwords back and forth in clear text. - If you want privacy, encrypt the whole session. Anything else is false security. +* Throw sensitive data such as passwords back and forth in clear text. If you + want privacy, encrypt the whole session. Anything else is false security. REST format URLs will generally take the form of: @@ -26,5 +27,4 @@ REST format URLs will generally take the form of: * FontAwesome for icons ## We are NOT using -* Your favorite javascript library -* Your favorite CSS framework +* Third party javascript libraries and/or CSS frameworks diff --git a/webcit-ng/server/caldav_reports.c b/webcit-ng/server/caldav_reports.c index 374d50ee5..8930bfb32 100644 --- a/webcit-ng/server/caldav_reports.c +++ b/webcit-ng/server/caldav_reports.c @@ -260,33 +260,60 @@ void caldav_report_one_item(struct http_transaction *h, struct ctdlsession *c, S // Compare function for "time-range" tests (RFC4791 section 9.9) // Returns nonzero if the supplied icalcomponent occurs within the specified time range +// +// IMPLEMENTATION NOTE: +// ical_ctdl_is_overlap() works because icaltime_compare() is really smart. +// It looks at the time zone of the dtstart/dtend and can apparently go back up the icalcomponent +// hierarchy to find its time zone data. I tested this by creating an event with a fictional +// time zone and it did the right thing. It even showed the fictional name to me. This saves us +// from having to convert everything to UTC before comparing. Nice! +// int caldav_time_range_filter_matches(icalcomponent *cal, char *start_str, char *end_str) { + struct icaltimetype dtstart = icalcomponent_get_dtstart(cal); + struct icaltimetype dtend = icalcomponent_get_dtend(cal); - // syslog(LOG_DEBUG, "caldav_time_range_filter_matches() comparing:\n\033[35m%s\033[0m", icalcomponent_as_ical_string(cal)); + struct icaltimetype search_start = icaltime_from_string(start_str); + syslog(LOG_DEBUG, " search start: \033[36m%-16s\033[0m (%s)", icaltime_as_ical_string_r(search_start), icaltime_get_tzid(search_start)); - // NOTE TO ME: - // Recurrence info is available at this level. We can handle it here. + struct icaltimetype search_end = icaltime_from_string(end_str); + syslog(LOG_DEBUG, " search end: \033[36m%-16s\033[0m (%s)", icaltime_as_ical_string_r(search_end), icaltime_get_tzid(search_end)); - // IMPLEMENTATION NOTE: - // ical_ctdl_is_overlap() works because icaltime_compare() is really smart. - // It looks at the time zone of the dtstart/dtend and can apparently go back up the icalcomponent - // hierarchy to find its time zone data. I tested this by creating an event with a fictional - // time zone and it did the right thing. It even showed the fictional name to me. This saves us - // from having to convert everything to UTC before comparing. Nice! + // If it is a recurring event, RRULE is available at this level. We can handle it here. - icaltimetype dts = icalcomponent_get_dtstart(cal); - syslog(LOG_DEBUG, "component start: \033[36m%-16s\033[0m (%s)", icaltime_as_ical_string_r(dts), icaltime_get_tzid(dts)); - - icaltimetype dte = icalcomponent_get_dtend(cal); - syslog(LOG_DEBUG, "component end: \033[36m%-16s\033[0m (%s)", icaltime_as_ical_string_r(dte), icaltime_get_tzid(dte)); + icalproperty *rrule; + rrule = icalcomponent_get_first_property(cal, ICAL_RRULE_PROPERTY); + if (rrule) { + if (icaltime_is_null_time(dtend)) { + dtend = dtstart; + } + struct icaldurationtype dur = icaltime_subtract(dtend, dtstart); // recurrences need duration to find dtend + struct icalrecurrencetype recur = icalproperty_get_rrule(rrule); + + icalrecur_iterator *ritr = icalrecur_iterator_new(recur, dtstart); // iterate through recurrences + int rcount = 0; + syslog(LOG_DEBUG, "\033[7m RECURRENCE: \033[0m"); + while (dtstart = icalrecur_iterator_next(ritr), !icaltime_is_null_time(dtstart)) { + dtend = icaltime_add(dtstart, dur); + syslog(LOG_DEBUG, "recurrence %3d start: \033[36m%-16s\033[0m (%s)", rcount, icaltime_as_ical_string_r(dtstart), icaltime_get_tzid(dtstart)); + syslog(LOG_DEBUG, "recurrence %3d end: \033[36m%-16s\033[0m (%s)", rcount, icaltime_as_ical_string_r(dtend), icaltime_get_tzid(dtend)); + + // Does THIS recurrence match the query? + if (ical_ctdl_is_overlap(dtstart, dtend, search_start, search_end)) { + icalrecur_iterator_free(ritr); + return(1); + } - struct icaltimetype start = icaltime_from_string(start_str); - syslog(LOG_DEBUG, " search start: \033[36m%-16s\033[0m (%s)", icaltime_as_ical_string_r(start), icaltime_get_tzid(start)); + ++rcount; + } - struct icaltimetype end = icaltime_from_string(end_str); - syslog(LOG_DEBUG, " search end: \033[36m%-16s\033[0m (%s)", icaltime_as_ical_string_r(end), icaltime_get_tzid(end)); + icalrecur_iterator_free(ritr); + return(0); // compared all recurrences, no match was found for any of them + } - return(ical_ctdl_is_overlap(dts, dte, start, end)); // We have a convenience function for this. + // For non recurring events, do a simple time range compare. + syslog(LOG_DEBUG, "event start: \033[36m%-16s\033[0m (%s)", icaltime_as_ical_string_r(dtstart), icaltime_get_tzid(dtstart)); + syslog(LOG_DEBUG, "event end: \033[36m%-16s\033[0m (%s)", icaltime_as_ical_string_r(dtend), icaltime_get_tzid(dtend)); + return(ical_ctdl_is_overlap(dtstart, dtend, search_start, search_end)); // We have a convenience function for this. } diff --git a/webcit/calendar.c b/webcit/calendar.c index f0e662226..b1da61c41 100644 --- a/webcit/calendar.c +++ b/webcit/calendar.c @@ -518,9 +518,7 @@ void display_individual_cal(icalcomponent *event, long msgnum, char *from, int u if (cptr) { /* Remove any existing DTSTART properties */ - while ( ps = icalcomponent_get_first_property(cptr, ICAL_DTSTART_PROPERTY), - ps != NULL - ) { + while (ps = icalcomponent_get_first_property(cptr, ICAL_DTSTART_PROPERTY), ps != NULL) { icalcomponent_remove_property(cptr, ps); } -- 2.30.2