/*====================================================================== FILE: icalspanlist.c CREATOR: ebusboom 23 aug 2000 (C) COPYRIGHT 2000, Eric Busboom This library is free software; you can redistribute it and/or modify it under the terms of either: The LGPL as published by the Free Software Foundation, version 2.1, available at: https://www.gnu.org/licenses/lgpl-2.1.html Or: The Mozilla Public License Version 2.0. You may obtain a copy of the License at https://www.mozilla.org/MPL/ ======================================================================*/ #ifdef HAVE_CONFIG_H #include #endif #include "icalspanlist.h" #include "icaltimezone.h" #include struct icalspanlist_impl { pvl_list spans; /**< list of icaltime_span data **/ struct icaltimetype start; /**< start time of span **/ struct icaltimetype end; /**< end time of span **/ }; /** @brief Internal comparison function for two spans * * @param a a spanlist. * @param b another spanlist. * * @return -1, 0, 1 depending on the comparison of the start times. * * Used to insert spans into the tree in sorted order. */ static int compare_span(void *a, void *b) { struct icaltime_span *span_a = (struct icaltime_span *)a; struct icaltime_span *span_b = (struct icaltime_span *)b; if (span_a->start == span_b->start) { return 0; } else if (span_a->start < span_b->start) { return -1; } else { /*if(span_a->start > span->b.start) */ return 1; } } /** @brief callback function for collecting spanlists of a * series of events. * * @param comp A valid icalcomponent. * @param span The span to insert into data. * @param data The actual spanlist to insert into * * This callback is used by icalcomponent_foreach_recurrence() * to build up a spanlist. */ static void icalspanlist_new_callback(icalcomponent *comp, struct icaltime_span *span, void *data) { icaltime_span *s; icalspanlist *sl = (icalspanlist *) data; _unused(comp); if (span->is_busy == 0) return; if ((s = (icaltime_span *) malloc(sizeof(icaltime_span))) == 0) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); return; } /* copy span data into allocated memory.. **/ *s = *span; pvl_insert_ordered(sl->spans, compare_span, (void *)s); } icalspanlist *icalspanlist_new(icalset *set, struct icaltimetype start, struct icaltimetype end) { struct icaltime_span range; pvl_elem itr; icalcomponent *c, *inner; icalcomponent_kind kind, inner_kind; icalspanlist *sl; struct icaltime_span *freetime; if ((sl = (struct icalspanlist_impl *)malloc(sizeof(struct icalspanlist_impl))) == 0) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); return 0; } sl->spans = pvl_newlist(); sl->start = start; sl->end = end; range.start = icaltime_as_timet(start); range.end = icaltime_as_timet(end); /* Gets a list of spans of busy time from the events in the set and order the spans based on the start time */ for (c = icalset_get_first_component(set); c != 0; c = icalset_get_next_component(set)) { kind = icalcomponent_isa(c); inner = icalcomponent_get_inner(c); if (!inner) continue; inner_kind = icalcomponent_isa(inner); if (kind != ICAL_VEVENT_COMPONENT && inner_kind != ICAL_VEVENT_COMPONENT) continue; icalerror_clear_errno(); icalcomponent_foreach_recurrence(c, start, end, icalspanlist_new_callback, (void *)sl); } /* Now Fill in the free time spans. loop through the spans. if the start of the range is not within the span, create a free entry that runs from the start of the range to the start of the span. */ for (itr = pvl_head(sl->spans); itr != 0; itr = pvl_next(itr)) { struct icaltime_span *s = (struct icaltime_span *)pvl_data(itr); if (!s) continue; if ((freetime = (struct icaltime_span *)malloc(sizeof(struct icaltime_span))) == 0) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); icalspanlist_free(sl); return 0; } if (range.start < s->start) { freetime->start = range.start; freetime->end = s->start; freetime->is_busy = 0; pvl_insert_ordered(sl->spans, compare_span, (void *)freetime); } else { free(freetime); } range.start = s->end; } /* If the end of the range is null, then assume that everything after the last item in the calendar is open and add a span that indicates this */ if (icaltime_is_null_time(end)) { struct icaltime_span *last_span; last_span = (struct icaltime_span *)pvl_data(pvl_tail(sl->spans)); if (last_span != 0) { if ((freetime = (struct icaltime_span *)malloc(sizeof(struct icaltime_span))) == 0) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); icalspanlist_free(sl); return 0; } freetime->is_busy = 0; freetime->start = last_span->end; freetime->end = freetime->start; pvl_insert_ordered(sl->spans, compare_span, (void *)freetime); } } return sl; } void icalspanlist_free(icalspanlist *s) { struct icaltime_span *span; if (s == NULL) return; while ((span = pvl_pop(s->spans)) != 0) { free(span); } pvl_free(s->spans); s->spans = 0; free(s); } void icalspanlist_dump(icalspanlist *sl) { int i = 0; pvl_elem itr; for (itr = pvl_head(sl->spans); itr != 0; itr = pvl_next(itr)) { struct icaltime_span *s = (struct icaltime_span *)pvl_data(itr); if (s) { printf("#%02d %d start: %s", ++i, s->is_busy, ctime(&s->start)); printf(" end : %s", ctime(&s->end)); } } } icalcomponent *icalspanlist_make_busy_list(icalspanlist *sl); struct icalperiodtype icalspanlist_next_free_time(icalspanlist *sl, struct icaltimetype t) { pvl_elem itr; struct icalperiodtype period; struct icaltime_span *s; time_t rangett = icaltime_as_timet(t); period.start = icaltime_null_time(); period.end = icaltime_null_time(); period.duration = icaldurationtype_null_duration(); itr = pvl_head(sl->spans); s = (struct icaltime_span *)pvl_data(itr); if (s == 0) { /* No elements in span */ return period; } /* Is the reference time before the first span? If so, assume that the reference time is free */ if (rangett < s->start) { /* End of period is start of first span if span is busy, end of the span if it is free */ period.start = t; if (s->is_busy == 1) { period.end = icaltime_from_timet_with_zone(s->start, 0, NULL); } else { period.end = icaltime_from_timet_with_zone(s->end, 0, NULL); } return period; } /* Otherwise, find the first free span that contains the reference time. */ for (itr = pvl_head(sl->spans); itr != 0; itr = pvl_next(itr)) { s = (struct icaltime_span *)pvl_data(itr); if (!s) continue; if (s->is_busy == 0 && s->start >= rangett && (rangett < s->end || s->end == s->start)) { if (rangett < s->start) { period.start = icaltime_from_timet_with_zone(s->start, 0, NULL); } else { period.start = icaltime_from_timet_with_zone(rangett, 0, NULL); } period.end = icaltime_from_timet_with_zone(s->end, 0, NULL); return period; } } period.start = icaltime_null_time(); period.end = icaltime_null_time(); return period; } int *icalspanlist_as_freebusy_matrix(icalspanlist *sl, int delta_t) { pvl_elem itr; time_t spanduration_secs; int *matrix; time_t matrix_slots; time_t sl_start, sl_end; icalerror_check_arg_rz((sl != 0), "spanlist"); if (!delta_t) delta_t = 3600; /* calculate the start and end time as time_t **/ sl_start = icaltime_as_timet_with_zone(sl->start, icaltimezone_get_utc_timezone()); sl_end = icaltime_as_timet_with_zone(sl->end, icaltimezone_get_utc_timezone()); /* insure that the time period falls on a time boundary divisible by delta_t */ sl_start /= delta_t; sl_start *= delta_t; sl_end /= delta_t; sl_end *= delta_t; /* find the duration of this spanlist **/ spanduration_secs = sl_end - sl_start; /* malloc our matrix, add one extra slot for a final -1 **/ matrix_slots = spanduration_secs / delta_t + 1; matrix = (int *)malloc((size_t)(sizeof(int) * matrix_slots)); if (matrix == NULL) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); return NULL; } memset(matrix, 0, (size_t)(sizeof(int) * matrix_slots)); matrix[matrix_slots - 1] = -1; /* loop through each span and mark the slots in the array */ for (itr = pvl_head(sl->spans); itr != 0; itr = pvl_next(itr)) { struct icaltime_span *s = (struct icaltime_span *)pvl_data(itr); if (s && s->is_busy == 1) { time_t offset_start = s->start / delta_t - sl_start / delta_t; time_t offset_end = (s->end - 1) / delta_t - sl_start / delta_t + 1; time_t i; if (offset_end >= matrix_slots) offset_end = matrix_slots - 1; for (i = offset_start; i < offset_end; i++) { matrix[i]++; } } } return matrix; } icalcomponent *icalspanlist_as_vfreebusy(icalspanlist *sl, const char *organizer, const char *attendee) { icalcomponent *comp; icalproperty *p; struct icaltimetype atime = icaltime_from_timet_with_zone(time(0), 0, NULL); pvl_elem itr; icaltimezone *utc_zone; icalparameter *param; if (!attendee) { icalerror_set_errno(ICAL_USAGE_ERROR); return 0; } utc_zone = icaltimezone_get_utc_timezone(); comp = icalcomponent_new_vfreebusy(); icalcomponent_add_property(comp, icalproperty_new_dtstart(sl->start)); icalcomponent_add_property(comp, icalproperty_new_dtend(sl->end)); icalcomponent_add_property(comp, icalproperty_new_dtstamp(atime)); if (organizer) { icalcomponent_add_property(comp, icalproperty_new_organizer(organizer)); } icalcomponent_add_property(comp, icalproperty_new_attendee(attendee)); /* now add the freebusy sections.. */ for (itr = pvl_head(sl->spans); itr != 0; itr = pvl_next(itr)) { struct icalperiodtype period; struct icaltime_span *s = (struct icaltime_span *)pvl_data(itr); if (s && s->is_busy == 1) { period.start = icaltime_from_timet_with_zone(s->start, 0, utc_zone); period.end = icaltime_from_timet_with_zone(s->end, 0, utc_zone); period.duration = icaldurationtype_null_duration(); p = icalproperty_new_freebusy(period); param = icalparameter_new_fbtype(ICAL_FBTYPE_BUSY); icalproperty_add_parameter(p, param); icalcomponent_add_property(comp, p); } } return comp; } icalspanlist *icalspanlist_from_vfreebusy(icalcomponent *comp) { icalcomponent *inner; icalproperty *prop; icalspanlist *sl; icalerror_check_arg_rz((comp != NULL), "comp"); inner = icalcomponent_get_inner(comp); if (!inner) return NULL; if ((sl = (icalspanlist *) malloc(sizeof(icalspanlist))) == 0) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); return 0; } sl->spans = pvl_newlist(); /* cycle through each FREEBUSY property, adding to the spanlist */ for (prop = icalcomponent_get_first_property(inner, ICAL_FREEBUSY_PROPERTY); prop != NULL; prop = icalcomponent_get_next_property(inner, ICAL_FREEBUSY_PROPERTY)) { icaltime_span *s = (icaltime_span *) malloc(sizeof(icaltime_span)); icalparameter *param; struct icalperiodtype period; icalparameter_fbtype fbtype; if (s == 0) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); icalspanlist_free(sl); return 0; } param = icalproperty_get_first_parameter(prop, ICAL_FBTYPE_PARAMETER); fbtype = (param) ? icalparameter_get_fbtype(param) : ICAL_FBTYPE_BUSY; switch (fbtype) { case ICAL_FBTYPE_FREE: case ICAL_FBTYPE_NONE: case ICAL_FBTYPE_X: s->is_busy = 1; break; default: s->is_busy = 0; } period = icalproperty_get_freebusy(prop); s->start = icaltime_as_timet_with_zone(period.start, icaltimezone_get_utc_timezone()); s->end = icaltime_as_timet_with_zone(period.end, icaltimezone_get_utc_timezone()); pvl_insert_ordered(sl->spans, compare_span, (void *)s); } /** @todo calculate start/end limits.. fill in holes? **/ return sl; }