/* -*- Mode: C -*- ====================================================================== FILE: icalspanlist.c CREATOR: ebusboom 23 aug 2000 $Id$ $Locker$ (C) COPYRIGHT 2000, Eric Busboom, http://www.softwarestudio.org This program 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: http://www.fsf.org/copyleft/lesser.html Or: The Mozilla Public License Version 1.0. You may obtain a copy of the License at http://www.mozilla.org/MPL/ ======================================================================*/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ical.h" #include "icalspanlist.h" #include /* for free and malloc */ #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; 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); } /** @brief Make a free list from a set of VEVENT components. * * @param set A valid icalset containing VEVENTS * @param start The free list starts at this date/time * @param end The free list ends at this date/time * * @return A spanlist corresponding to the VEVENTS * * Given a set of components, a start time and an end time * return a spanlist that contains the free/busy times. */ 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); /* Get 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 ((freetime=(struct icaltime_span *) malloc(sizeof(struct icaltime_span))) == 0){ icalerror_set_errno(ICAL_NEWFAILED_ERROR); 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); 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; } /** @brief Destructor. * @param s A valid icalspanlist * * Free memory associated with the spanlist */ 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); } /** @brief (Debug) print out spanlist to stdout. * @param sl A valid icalspanlist. */ 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); printf("#%02d %d start: %s",++i,s->is_busy,ctime(&s->start)); printf(" end : %s",ctime(&s->end)); } } icalcomponent* icalspanlist_make_free_list(icalspanlist* sl); icalcomponent* icalspanlist_make_busy_list(icalspanlist* sl); /** @brief Find next free time span in a spanlist. * * @param sl The spanlist to search. * @param t The time to start looking. * * Given a spanlist and a time, find the next period of time * that is free */ 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(); 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 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(s->start,0); } else { period.end = icaltime_from_timet(s->end,0); } 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->is_busy == 0 && s->start >= rangett && ( rangett < s->end || s->end == s->start)){ if (rangett < s->start){ period.start = icaltime_from_timet(s->start,0); } else { period.start = icaltime_from_timet(rangett,0); } period.end = icaltime_from_timet(s->end,0); return period; } } period.start = icaltime_null_time(); period.end = icaltime_null_time(); return period; } struct icalperiodtype icalspanlist_next_busy_time(icalspanlist* sl, struct icaltimetype t); /** @brief Returns an hour-by-hour array of free/busy times over a * given period. * * @param sl A valid icalspanlist * @param delta_t The time slice to divide by, in seconds. Default 3600. * * @return A pointer to an array of integers containing the number of * busy events in each delta_t time period. The final entry * contains the value -1. * * This calculation is somewhat tricky. This is due to the fact that * the time range contains the start time, but does not contain the * end time. To perform a proper calculation we subtract one second * off the end times to get a true containing time. * * Also note that if you supplying a spanlist that does not start or * end on a time boundary divisible by delta_t you may get results * that are not quite what you expect. */ int* icalspanlist_as_freebusy_matrix(icalspanlist* sl, int delta_t) { pvl_elem itr; int spanduration_secs; int *matrix; int 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 divisable 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(sizeof(int) * matrix_slots); if (matrix == NULL) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); return NULL; } memset(matrix, 0, 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->is_busy == 1) { int offset_start = s->start/delta_t - sl_start/delta_t; int offset_end = (s->end - 1) /delta_t - sl_start/delta_t + 1; int i; if (offset_end >= matrix_slots) offset_end = matrix_slots - 1; i = offset_start; for (i=offset_start; i < offset_end; i++) { matrix[i]++; } } } return matrix; } /** @brief Return a VFREEBUSY component for the corresponding spanlist * * @param sl A valid icalspanlist, from icalspanlist_new() * @param organizer The organizer specified as MAILTO:user@domain * @param attendee The attendee specified as MAILTO:user@domain * * @return A valid icalcomponent or NULL. * * This function returns a VFREEBUSY component for the given spanlist. * The start time is mapped to DTSTART, the end time to DTEND. * Each busy span is represented as a separate FREEBUSY entry. * An attendee parameter is required, and organizer parameter is * optional. */ icalcomponent *icalspanlist_as_vfreebusy(icalspanlist* sl, const char* organizer, const char* attendee) { icalcomponent *comp; icalproperty *p; struct icaltimetype atime = icaltime_from_timet( time(0),0); 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->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; } /** @brief Return a spanlist corresponding to the VFREEBUSY portion of * an icalcomponent. * * @param c A valid icalcomponent. * * @return A valid icalspanlist or NULL if no VFREEBUSY section. * */ 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); 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; 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; }