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.
 
 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.
 
 ## 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.
 * 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:
 
 
 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
 * 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
 
 // 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) {
 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 */
                        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);
                                }
 
                                        icalcomponent_remove_property(cptr, ps);
                                }