Completed time range search of recurring events.
authorArt Cancro <ajc@citadel.org>
Mon, 25 Mar 2024 17:58:29 +0000 (10:58 -0700)
committerArt Cancro <ajc@citadel.org>
Mon, 25 Mar 2024 19:02:36 +0000 (12:02 -0700)
webcit-ng/README.md
webcit-ng/server/caldav_reports.c
webcit/calendar.c

index 95ab1f80e9bf8db25b4f0c18b957dfd598d8d50b..bcae8a78b5b4ed37b30f89834254a1f9fedb4080 100644 (file)
@@ -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
index 374d50ee5f754842dadd2fadce83e81e40641a8b..8930bfb328b325d626323b586498c67aef3eeab1 100644 (file)
@@ -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.
 }
 
 
index f0e662226258aaf8757b1ae3a353041cb40ef883..b1da61c4182a6706362e192c4ea24242c7a4a07d 100644 (file)
@@ -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);
                                }