X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=webcit%2Fcalendar.c;h=2897ba20188d9a007eeb1623af1fee2709f5e3e7;hb=4b4dc864ede7c5d8d956febe4a0afb422b78e7c4;hp=7e81620e428b8bd8ee72773bac99b4110aede83d;hpb=9f145319b92b196662aa51cb2e3d7c392629965e;p=citadel.git diff --git a/webcit/calendar.c b/webcit/calendar.c index 7e81620e4..2897ba201 100644 --- a/webcit/calendar.c +++ b/webcit/calendar.c @@ -1,26 +1,41 @@ /* - * $Id$ - * * Functions which handle calendar objects and their processing/display. + * + * Copyright (c) 1996-2011 by the citadel.org team + * + * This program is open source software. You can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "webcit.h" #include "webserver.h" - +#include "calendar.h" /* - * \brief Process a calendar object - * ...at this point it's already been deserialized by cal_process_attachment() - * \param cal the calendar object - * \param recursion_level call stack depth ?????? - * \param msgnum number of the mesage in our db - * \param cal_partnum of the calendar object ???? + * Process a calendar object. At this point it's already been deserialized by cal_process_attachment() + * + * cal: the calendar object + * recursion_level: Number of times we've recursed into this function + * msgnum: Message number on the Citadel server + * cal_partnum: MIME part number within that message containing the calendar object */ -void cal_process_object(icalcomponent *cal, +void cal_process_object(StrBuf *Target, + icalcomponent *cal, int recursion_level, long msgnum, - char *cal_partnum -) { + const char *cal_partnum) +{ icalcomponent *c; icalproperty *method = NULL; icalproperty_method the_method = ICAL_METHOD_NONE; @@ -33,72 +48,81 @@ void cal_process_object(icalcomponent *cal, int is_update = 0; char divname[32]; static int divcount = 0; + const char *ch; sprintf(divname, "rsvp%04x", ++divcount); - /** Leading HTML for the display of this object */ + /* Convert timezones to something easy to display. + * It's safe to do this in memory because we're only changing it on the + * display side -- when we tell the server to do something with the object, + * the server will be working with its original copy in the database. + */ + if ((cal) && (recursion_level == 0)) { + ical_dezonify(cal); + } + + /* Leading HTML for the display of this object */ if (recursion_level == 0) { - wprintf("
\n"); + StrBufAppendPrintf(Target, "
\n"); } - /** Look for a method */ + /* Look for a method */ method = icalcomponent_get_first_property(cal, ICAL_METHOD_PROPERTY); - /** See what we need to do with this */ + /* See what we need to do with this */ if (method != NULL) { - the_method = icalproperty_get_method(method); char *title; + the_method = icalproperty_get_method(method); - wprintf("
", divname); - wprintf(""); - wprintf(""); + StrBufAppendPrintf(Target, "
", divname); + StrBufAppendPrintf(Target, ""); + StrBufAppendPrintf(Target, ""); switch(the_method) { - case ICAL_METHOD_REQUEST: + case ICAL_METHOD_REQUEST: title = _("Meeting invitation"); break; - case ICAL_METHOD_REPLY: + case ICAL_METHOD_REPLY: title = _("Attendee's reply to your invitation"); break; - case ICAL_METHOD_PUBLISH: + case ICAL_METHOD_PUBLISH: title = _("Published event"); break; - default: + default: title = _("This is an unknown type of calendar item."); break; } - wprintf(""); + StrBufAppendPrintf(Target, ""); - wprintf("  %s",title); - wprintf("
"); + StrBufAppendPrintf(Target, "  %s",title); + StrBufAppendPrintf(Target, "
"); } - wprintf("
"); + StrBufAppendPrintf(Target, "
"); p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY); if (p != NULL) { - wprintf("
"); - wprintf(_("Summary:")); - wprintf("
"); - escputs((char *)icalproperty_get_comment(p)); - wprintf("
\n"); + StrBufAppendPrintf(Target, "
"); + StrBufAppendPrintf(Target, _("Summary:")); + StrBufAppendPrintf(Target, "
"); + StrEscAppend(Target, NULL, (char *)icalproperty_get_comment(p), 0, 0); + StrBufAppendPrintf(Target, "
\n"); } p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY); if (p != NULL) { - wprintf("
"); - wprintf(_("Location:")); - wprintf("
"); - escputs((char *)icalproperty_get_comment(p)); - wprintf("
\n"); + StrBufAppendPrintf(Target, "
"); + StrBufAppendPrintf(Target, _("Location:")); + StrBufAppendPrintf(Target, "
"); + StrEscAppend(Target, NULL, (char *)icalproperty_get_comment(p), 0, 0); + StrBufAppendPrintf(Target, "
\n"); } - /** + /* * Only show start/end times if we're actually looking at the VEVENT * component. Otherwise it shows bogus dates for things like timezone. */ if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) { - p = icalcomponent_get_first_property(cal, - ICAL_DTSTART_PROPERTY); + p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY); if (p != NULL) { t = icalproperty_get_dtstart(p); @@ -110,16 +134,16 @@ void cal_process_object(icalcomponent *cal, d_tm.tm_mon = t.month - 1; d_tm.tm_mday = t.day; wc_strftime(d_str, sizeof d_str, "%x", &d_tm); - wprintf("
"); - wprintf(_("Date:")); - wprintf("
%s
", d_str); + StrBufAppendPrintf(Target, "
"); + StrBufAppendPrintf(Target, _("Date:")); + StrBufAppendPrintf(Target, "
%s
", d_str); } else { tt = icaltime_as_timet(t); - webcit_fmt_date(buf, tt, 0); - wprintf("
"); - wprintf(_("Starting date/time:")); - wprintf("
%s
", buf); + webcit_fmt_date(buf, 256, tt, DATEFMT_FULL); + StrBufAppendPrintf(Target, "
"); + StrBufAppendPrintf(Target, _("Starting date/time:")); + StrBufAppendPrintf(Target, "
%s
", buf); } } @@ -127,57 +151,67 @@ void cal_process_object(icalcomponent *cal, if (p != NULL) { t = icalproperty_get_dtend(p); tt = icaltime_as_timet(t); - webcit_fmt_date(buf, tt, 0); - wprintf("
"); - wprintf(_("Ending date/time:")); - wprintf("
%s
", buf); + webcit_fmt_date(buf, 256, tt, DATEFMT_FULL); + StrBufAppendPrintf(Target, "
"); + StrBufAppendPrintf(Target, _("Ending date/time:")); + StrBufAppendPrintf(Target, "
%s
", buf); } } p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY); if (p != NULL) { - wprintf("
"); - wprintf(_("Description:")); - wprintf("
"); - escputs((char *)icalproperty_get_comment(p)); - wprintf("
\n"); + StrBufAppendPrintf(Target, "
"); + StrBufAppendPrintf(Target, _("Description:")); + StrBufAppendPrintf(Target, "
"); + StrEscAppend(Target, NULL, (char *)icalproperty_get_comment(p), 0, 0); + StrBufAppendPrintf(Target, "
\n"); + } + + if (icalcomponent_get_first_property(cal, ICAL_RRULE_PROPERTY)) { + /* Unusual string syntax used here in order to re-use existing translations */ + StrBufAppendPrintf(Target, "
%s:
%s.
\n", + _("Recurrence"), + _("This is a recurring event") + ); } - /** If the component has attendees, iterate through them. */ - for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) { - wprintf("
"); - wprintf(_("Attendee:")); - wprintf("
"); - safestrncpy(buf, icalproperty_get_attendee(p), sizeof buf); - if (!strncasecmp(buf, "MAILTO:", 7)) { + /* If the component has attendees, iterate through them. */ + for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); + (p != NULL); + p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) { + StrBufAppendPrintf(Target, "
"); + StrBufAppendPrintf(Target, _("Attendee:")); + StrBufAppendPrintf(Target, "
"); + ch = icalproperty_get_attendee(p); + if ((ch != NULL) && !strncasecmp(buf, "MAILTO:", 7)) { /** screen name or email address */ - strcpy(buf, &buf[7]); + safestrncpy(buf, ch + 7, sizeof(buf)); striplt(buf); - escputs(buf); - wprintf(" "); + StrEscAppend(Target, NULL, buf, 0, 0); + StrBufAppendPrintf(Target, " "); /** participant status */ partstat_as_string(buf, p); - escputs(buf); + StrEscAppend(Target, NULL, buf, 0, 0); } - wprintf("
\n"); + StrBufAppendPrintf(Target, "\n"); } - /** If the component has subcomponents, recurse through them. */ + /* If the component has subcomponents, recurse through them. */ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT); - (c != 0); - c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) { + (c != 0); + c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) { /* Recursively process subcomponent */ - cal_process_object(c, recursion_level+1, msgnum, cal_partnum); + cal_process_object(Target, c, recursion_level+1, msgnum, cal_partnum); } - /** If this is a REQUEST, display conflicts and buttons */ + /* If this is a REQUEST, display conflicts and buttons */ if (the_method == ICAL_METHOD_REQUEST) { /* Check for conflicts */ - lprintf(9, "Checking server calendar for conflicts...\n"); + syslog(9, "Checking server calendar for conflicts...\n"); serv_printf("ICAL conflicts|%ld|%s|", msgnum, cal_partnum); serv_getln(buf, sizeof buf); if (buf[0] == '1') { @@ -187,30 +221,30 @@ void cal_process_object(icalcomponent *cal, if (is_update) { snprintf(conflict_message, sizeof conflict_message, - _("This is an update of '%s' which is already in your calendar."), conflict_name); + _("This is an update of '%s' which is already in your calendar."), conflict_name); } else { snprintf(conflict_message, sizeof conflict_message, - _("This event would conflict with '%s' which is already in your calendar."), conflict_name); + _("This event would conflict with '%s' which is already in your calendar."), conflict_name); } - wprintf("
%s", + StrBufAppendPrintf(Target, "
%s", (is_update ? - _("Update:") : - _("CONFLICT:") - ) - ); - wprintf("
"); - escputs(conflict_message); - wprintf("
\n"); + _("Update:") : + _("CONFLICT:") + ) + ); + StrBufAppendPrintf(Target, "
"); + StrEscAppend(Target, NULL, conflict_message, 0, 0); + StrBufAppendPrintf(Target, "
\n"); } } - lprintf(9, "...done.\n"); + syslog(9, "...done.\n"); - wprintf("
"); + StrBufAppendPrintf(Target, "
"); - /** Display the Accept/Decline buttons */ - wprintf("

" + /* Display the Accept/Decline buttons */ + StrBufAppendPrintf(Target, "

" "%s " "    " "%s" @@ -224,22 +258,15 @@ void cal_process_object(icalcomponent *cal, divname, divname, msgnum, cal_partnum, _("Accept"), divname, divname, msgnum, cal_partnum, _("Tentative"), divname, divname, msgnum, cal_partnum, _("Decline") - ); + ); } - /** If this is a REPLY, display update button */ + /* If this is a REPLY, display update button */ if (the_method == ICAL_METHOD_REPLY) { - /** \todo In the future, if we want to validate this object before \ - * continuing, we can do it this way: - serv_printf("ICAL whatever|%ld|%s|", msgnum, cal_partnum); - serv_getln(buf, sizeof buf); - } - ***********/ - - /** Display the update buttons */ - wprintf("

" + /* Display the update buttons */ + StrBufAppendPrintf(Target, "

" "%s " "    " "%s" @@ -250,38 +277,33 @@ void cal_process_object(icalcomponent *cal, _("Click Update to accept this reply and update your calendar."), divname, divname, msgnum, cal_partnum, _("Update"), divname, divname, msgnum, cal_partnum, _("Ignore") - ); - + ); + } - - /** Trailing HTML for the display of this object */ + + /* Trailing HTML for the display of this object */ if (recursion_level == 0) { - wprintf("

 

\n"); + StrBufAppendPrintf(Target, "

 

\n"); } } -/** - * \brief process calendar mail atachment - * Deserialize a calendar object in a message so it can be processed. - * (This is the main entry point for these things) - * \param part_source the part of the message we want to parse - * \param msgnum number of the mesage in our db - * \param cal_partnum the number of the calendar item +/* + * Deserialize a calendar object in a message so it can be displayed. */ -void cal_process_attachment(char *part_source, long msgnum, char *cal_partnum) { +void cal_process_attachment(wc_mime_attachment *Mime) +{ icalcomponent *cal; - cal = icalcomponent_new_from_string(part_source); - + cal = icalcomponent_new_from_string(ChrPtr(Mime->Data)); + FlushStrBuf(Mime->Data); if (cal == NULL) { - wprintf(_("There was an error parsing this calendar item.")); - wprintf("
\n"); + StrBufAppendPrintf(Mime->Data, _("There was an error parsing this calendar item.")); + StrBufAppendPrintf(Mime->Data, "
\n"); return; } - ical_dezonify(cal); - cal_process_object(cal, 0, msgnum, cal_partnum); + cal_process_object(Mime->Data, cal, 0, Mime->msgnum, ChrPtr(Mime->PartNum)); /* Free the memory we obtained from libical's constructor */ icalcomponent_free(cal); @@ -290,11 +312,11 @@ void cal_process_attachment(char *part_source, long msgnum, char *cal_partnum) { -/** - * \brief accept/decline meeting - * Respond to a meeting request +/* + * Respond to a meeting request - accept/decline meeting */ -void respond_to_request(void) { +void respond_to_request(void) +{ char buf[1024]; begin_ajax_response(); @@ -307,27 +329,27 @@ void respond_to_request(void) { serv_getln(buf, sizeof buf); if (buf[0] == '2') { - wprintf(""); + wc_printf(""); if (!strcasecmp(bstr("sc"), "accept")) { - wprintf(_("You have accepted this meeting invitation. " + wc_printf(_("You have accepted this meeting invitation. " "It has been entered into your calendar.") ); } else if (!strcasecmp(bstr("sc"), "tentative")) { - wprintf(_("You have tentatively accepted this meeting invitation. " + wc_printf(_("You have tentatively accepted this meeting invitation. " "It has been 'pencilled in' to your calendar.") ); } else if (!strcasecmp(bstr("sc"), "decline")) { - wprintf(_("You have declined this meeting invitation. " - "It has not been entered into your calendar.") - ); + wc_printf(_("You have declined this meeting invitation. " + "It has not been entered into your calendar.") + ); } - wprintf(" "); - wprintf(_("A reply has been sent to the meeting organizer.")); - wprintf(""); + wc_printf(" "); + wc_printf(_("A reply has been sent to the meeting organizer.")); + wc_printf(""); } else { - wprintf(""); - wprintf("%s\n", &buf[4]); - wprintf(""); + wc_printf(""); + wc_printf("%s\n", &buf[4]); + wc_printf(""); } end_ajax_response(); @@ -335,10 +357,11 @@ void respond_to_request(void) { -/** - * \brief Handle an incoming RSVP +/* + * Handle an incoming RSVP */ -void handle_rsvp(void) { +void handle_rsvp(void) +{ char buf[1024]; begin_ajax_response(); @@ -351,503 +374,434 @@ void handle_rsvp(void) { serv_getln(buf, sizeof buf); if (buf[0] == '2') { - wprintf(""); + wc_printf(""); if (!strcasecmp(bstr("sc"), "update")) { - wprintf(_("Your calendar has been updated to reflect this RSVP.")); + /* Translators: RSVP aka Répondez s'il-vous-plaît Is the term + that the recipient of an ical-invitation should please + answer this request. */ + wc_printf(_("Your calendar has been updated to reflect this RSVP.")); } else if (!strcasecmp(bstr("sc"), "ignore")) { - wprintf(_("You have chosen to ignore this RSVP. " - "Your calendar has not been updated.") - ); + wc_printf(_("You have chosen to ignore this RSVP. " + "Your calendar has not been updated.") + ); } - wprintf(""); + wc_printf(""); } else { - wprintf(" %s\n", &buf[4]); - wprintf(""); + wc_printf(" %s\n", &buf[4]); + wc_printf(""); } end_ajax_response(); - } -/*@}*/ -/*-----------------------------------------------------------------------**/ - - -/** - * \defgroup MsgDisplayHandlers Display handlers for message reading - * \ingroup Calendaring - */ - -/*@{*/ - - - -/** - * \brief get items, keep them. - * If we're reading calendar items, just store them for now. We have to - * sort and re-output them later when we draw the calendar. - * \param cal Our calendar to process - * \param msgnum number of the mesage in our db +/* + * free memory allocated using libical */ -void display_individual_cal(icalcomponent *cal, long msgnum, char *from, int unread) +void delete_cal(void *vCal) { - struct wcsession *WCC = WC; /* stack this for faster access (WC is a function) */ - - WCC->num_cal += 1; - WCC->disp_cal = realloc(WC->disp_cal, (sizeof(struct disp_cal) * WCC->num_cal) ); - WCC->disp_cal[WCC->num_cal - 1].cal = icalcomponent_new_clone(cal); - WCC->disp_cal[WCC->num_cal - 1].unread = unread; - WCC->disp_cal[WCC->num_cal - 1].from = malloc (strlen(from) + 1); - strcpy (WCC->disp_cal[WCC->num_cal - 1].from, from); - ical_dezonify(WCC->disp_cal[WCC->num_cal - 1].cal); - WCC->disp_cal[WCC->num_cal - 1].cal_msgnum = msgnum; + disp_cal *Cal = (disp_cal*) vCal; + icalcomponent_free(Cal->cal); + free(Cal->from); + free(Cal); } - - /* - * \brief edit a task - * Display a task by itself (for editing) - * \param supplied_vtodo the todo item we want to edit - * \param msgnum number of the mesage in our db + * This is the meat-and-bones of the first part of our two-phase calendar display. + * As we encounter calendar items in messages being read from the server, we break out + * any iCalendar objects and store them in a hash table. Later on, the second phase will + * use this hash table to render the calendar for display. */ -void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum, char *from, int unread) { - icalcomponent *vtodo; - icalproperty *p; - struct icaltimetype t; - time_t now; - int created_new_vtodo = 0; - - now = time(NULL); - - if (supplied_vtodo != NULL) { - vtodo = supplied_vtodo; - - /** - * If we're looking at a fully encapsulated VCALENDAR - * rather than a VTODO component, attempt to use the first - * relevant VTODO subcomponent. If there is none, the - * NULL returned by icalcomponent_get_first_component() will - * tell the next iteration of this function to create a - * new one. - */ - if (icalcomponent_isa(vtodo) == ICAL_VCALENDAR_COMPONENT) { - display_edit_individual_task( - icalcomponent_get_first_component( - vtodo, ICAL_VTODO_COMPONENT - ), - msgnum, - from, unread - ); - return; - } - } - else { - vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT); - created_new_vtodo = 1; +void display_individual_cal(icalcomponent *event, long msgnum, char *from, int unread, calview *calv) +{ + icalproperty *ps = NULL; + struct icaltimetype dtstart, dtend; + struct icaldurationtype dur; + wcsession *WCC = WC; + disp_cal *Cal; + size_t len; + time_t final_recurrence = 0; + icalcomponent *cptr = NULL; + + /* recur variables */ + icalproperty *rrule = NULL; + struct icalrecurrencetype recur; + icalrecur_iterator *ritr = NULL; + struct icaltimetype next; + int num_recur = 0; + int stop_rr = 0; + + /* first and foremost, check for bogosity. bail if we see no DTSTART property */ + + if (icalcomponent_get_first_property(icalcomponent_get_first_component( + event, ICAL_VEVENT_COMPONENT), ICAL_DTSTART_PROPERTY) == NULL) + { + return; } - output_headers(1, 1, 2, 0, 0, 0); - wprintf("
\n"); - wprintf(""); - wprintf("

"); - wprintf(_("Edit task")); - wprintf("

"); - wprintf("
\n"); - - wprintf("
\n"); + /* ok, chances are we've got a live one here. let's try to figure out where it goes. */ - wprintf("
" - "
"); + dtstart = icaltime_null_time(); + dtend = icaltime_null_time(); - wprintf("
\n"); - wprintf("\n", WC->nonce); - wprintf("\n", - msgnum); - - wprintf("\n"); - - wprintf("\n"); - wprintf("\n"); - wprintf("\n"); - wprintf("
"); - wprintf(_("Summary:")); - wprintf("" - "disp_cal_items == NULL) { + WCC->disp_cal_items = NewHash(0, Flathash); + } + + /* Note: anything we do here, we also have to do below for the recurrences. */ + Cal = (disp_cal*) malloc(sizeof(disp_cal)); + memset(Cal, 0, sizeof(disp_cal)); + Cal->cal = icalcomponent_new_clone(event); + + /* Dezonify and decapsulate at the very last moment */ + ical_dezonify(Cal->cal); + if (icalcomponent_isa(Cal->cal) != ICAL_VEVENT_COMPONENT) { + cptr = icalcomponent_get_first_component(Cal->cal, ICAL_VEVENT_COMPONENT); + if (cptr) { + cptr = icalcomponent_new_clone(cptr); + icalcomponent_free(Cal->cal); + Cal->cal = cptr; + } } - wprintf("\">
"); - wprintf(_("Start date:")); - wprintf(""); - p = icalcomponent_get_first_property(vtodo, ICAL_DTSTART_PROPERTY); - if (p != NULL) { - t = icalproperty_get_dtstart(p); - } - else { - t = icaltime_from_timet(now, 0); + Cal->unread = unread; + len = strlen(from); + Cal->from = (char*)malloc(len+ 1); + memcpy(Cal->from, from, len + 1); + Cal->cal_msgnum = msgnum; + + /* Precalculate the starting date and time of this event, and store it in our top-level + * structure. Later, when we are rendering the calendar, we can just peek at these values + * without having to break apart every calendar item. + */ + ps = icalcomponent_get_first_property(Cal->cal, ICAL_DTSTART_PROPERTY); + if (ps != NULL) { + dtstart = icalproperty_get_dtstart(ps); + Cal->event_start = icaltime_as_timet(dtstart); } - display_icaltimetype_as_webform(&t, "dtstart"); - wprintf("
"); - wprintf(_("Due date:")); - wprintf(""); - p = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY); - if (p != NULL) { - t = icalproperty_get_due(p); + /* Do the same for the ending date and time. It makes the day view much easier to render. */ + ps = icalcomponent_get_first_property(Cal->cal, ICAL_DTEND_PROPERTY); + if (ps != NULL) { + dtend = icalproperty_get_dtend(ps); + Cal->event_end = icaltime_as_timet(dtend); } - else { - t = icaltime_from_timet(now, 0); - } - display_icaltimetype_as_webform(&t, "due"); - wprintf("
"); - wprintf(_("Description:")); - wprintf(""); - wprintf("
\n"); - - wprintf("
" - "" - "  " - "\n" - "  " - "\n" - "
\n", - _("Save"), - _("Delete"), - _("Cancel") - ); - wprintf("
\n"); + /* Store it in the hash list. */ + /* syslog(LOG_DEBUG, "INITIAL: %s", ctime(&Cal->event_start)); */ + Put(WCC->disp_cal_items, + (char*) &Cal->event_start, + sizeof(Cal->event_start), + Cal, + delete_cal); - wprintf("
\n"); - wDumpContent(1); + /****************************** handle recurring events ******************************/ + + if (icaltime_is_null_time(dtstart)) return; /* Can't recur without a start time */ - if (created_new_vtodo) { - icalcomponent_free(vtodo); + if (!icaltime_is_null_time(dtend)) { /* Need duration for recurrences */ + dur = icaltime_subtract(dtend, dtstart); } -} -/* - * \brief Save an edited task - * \param supplied_vtodo the task to save - * \param msgnum number of the mesage in our db - */ -void save_individual_task(icalcomponent *supplied_vtodo, long msgnum, char* from, int unread) { - char buf[SIZ]; - int delete_existing = 0; - icalproperty *prop; - icalcomponent *vtodo, *encaps; - int created_new_vtodo = 0; - int i; - int sequence = 0; - struct icaltimetype t; + /* + * Just let libical iterate the recurrence, and keep looping back to the top of this function, + * adding new hash entries that all point back to the same msgnum, until either the iteration + * stops or some outer bound is reached. The display code will automatically do the Right Thing. + */ + cptr = event; + if (icalcomponent_isa(cptr) != ICAL_VEVENT_COMPONENT) { + cptr = icalcomponent_get_first_component(cptr, ICAL_VEVENT_COMPONENT); + } + if (!cptr) return; + ps = icalcomponent_get_first_property(cptr, ICAL_DTSTART_PROPERTY); + if (ps == NULL) return; + dtstart = icalproperty_get_dtstart(ps); + rrule = icalcomponent_get_first_property(cptr, ICAL_RRULE_PROPERTY); + if (!rrule) return; + recur = icalproperty_get_rrule(rrule); + ritr = icalrecur_iterator_new(recur, dtstart); + if (!ritr) return; + + while (next = icalrecur_iterator_next(ritr), ((!icaltime_is_null_time(next))&&(!stop_rr)) ) { + ++num_recur; + if (num_recur > 1) { /* Skip the first one. We already did it at the root. */ + icalcomponent *cptr; + + /* Note: anything we do here, we also have to do above for the root event. */ + Cal = (disp_cal*) malloc(sizeof(disp_cal)); + memset(Cal, 0, sizeof(disp_cal)); + Cal->cal = icalcomponent_new_clone(event); + Cal->unread = unread; + len = strlen(from); + Cal->from = (char*)malloc(len+ 1); + memcpy(Cal->from, from, len + 1); + Cal->cal_msgnum = msgnum; + + if (icalcomponent_isa(Cal->cal) == ICAL_VEVENT_COMPONENT) { + cptr = Cal->cal; + } + else { + cptr = icalcomponent_get_first_component(Cal->cal, ICAL_VEVENT_COMPONENT); + } + if (cptr) { - if (supplied_vtodo != NULL) { - vtodo = supplied_vtodo; - /** - * If we're looking at a fully encapsulated VCALENDAR - * rather than a VTODO component, attempt to use the first - * relevant VTODO subcomponent. If there is none, the - * NULL returned by icalcomponent_get_first_component() will - * tell the next iteration of this function to create a - * new one. - */ - if (icalcomponent_isa(vtodo) == ICAL_VCALENDAR_COMPONENT) { - save_individual_task( - icalcomponent_get_first_component( - vtodo, ICAL_VTODO_COMPONENT), - msgnum, from, unread - ); - return; - } - } - else { - vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT); - created_new_vtodo = 1; - } + /* Remove any existing DTSTART properties */ + while ( ps = icalcomponent_get_first_property(cptr, ICAL_DTSTART_PROPERTY), + ps != NULL + ) { + icalcomponent_remove_property(cptr, ps); + } - if (havebstr("save_button")) { + /* Add our shiny new DTSTART property from the iteration */ + ps = icalproperty_new_dtstart(next); + icalcomponent_add_property(cptr, ps); + Cal->event_start = icaltime_as_timet(next); + final_recurrence = Cal->event_start; + + /* Remove any existing DTEND properties */ + while ( ps = icalcomponent_get_first_property(cptr, ICAL_DTEND_PROPERTY), + (ps != NULL) + ) { + icalcomponent_remove_property(cptr, ps); + } - /** Replace values in the component with ones from the form */ + /* Add our shiny new DTEND property from the iteration */ + ps = icalproperty_new_dtend(icaltime_add(next, dur)); + icalcomponent_add_property(cptr, ps); - while (prop = icalcomponent_get_first_property(vtodo, - ICAL_SUMMARY_PROPERTY), prop != NULL) { - icalcomponent_remove_property(vtodo, prop); - icalproperty_free(prop); - } - if (havebstr("summary")) { - - icalcomponent_add_property(vtodo, - icalproperty_new_summary(bstr("summary"))); - } else { - icalcomponent_add_property(vtodo, - icalproperty_new_summary("Untitled Task")); - } - - while (prop = icalcomponent_get_first_property(vtodo, - ICAL_DESCRIPTION_PROPERTY), prop != NULL) { - icalcomponent_remove_property(vtodo, prop); - icalproperty_free(prop); - } - icalcomponent_add_property(vtodo, - icalproperty_new_description(bstr("description"))); - - while (prop = icalcomponent_get_first_property(vtodo, - ICAL_DTSTART_PROPERTY), prop != NULL) { - icalcomponent_remove_property(vtodo, prop); - icalproperty_free(prop); - } - icaltime_from_webform(&t, "dtstart"); - icalcomponent_add_property(vtodo, - icalproperty_new_dtstart(t) - ); - - while (prop = icalcomponent_get_first_property(vtodo, - ICAL_DUE_PROPERTY), prop != NULL) { - icalcomponent_remove_property(vtodo, prop); - icalproperty_free(prop); - } - icaltime_from_webform(&t, "due"); - icalcomponent_add_property(vtodo, - icalproperty_new_due(t) - ); + } - /** Give this task a UID if it doesn't have one. */ - lprintf(9, "Give this task a UID if it doesn't have one.\n"); - if (icalcomponent_get_first_property(vtodo, - ICAL_UID_PROPERTY) == NULL) { - generate_uuid(buf); - icalcomponent_add_property(vtodo, - icalproperty_new_uid(buf) - ); - } + /* Dezonify and decapsulate at the very last moment */ + ical_dezonify(Cal->cal); + if (icalcomponent_isa(Cal->cal) != ICAL_VEVENT_COMPONENT) { + cptr = icalcomponent_get_first_component(Cal->cal, ICAL_VEVENT_COMPONENT); + if (cptr) { + cptr = icalcomponent_new_clone(cptr); + icalcomponent_free(Cal->cal); + Cal->cal = cptr; + } + } - /** Increment the sequence ID */ - lprintf(9, "Increment the sequence ID\n"); - while (prop = icalcomponent_get_first_property(vtodo, - ICAL_SEQUENCE_PROPERTY), (prop != NULL) ) { - i = icalproperty_get_sequence(prop); - lprintf(9, "Sequence was %d\n", i); - if (i > sequence) sequence = i; - icalcomponent_remove_property(vtodo, prop); - icalproperty_free(prop); - } - ++sequence; - lprintf(9, "New sequence is %d. Adding...\n", sequence); - icalcomponent_add_property(vtodo, - icalproperty_new_sequence(sequence) - ); + if ( (Cal->event_start > calv->lower_bound) + && (Cal->event_start < calv->upper_bound) + ) { + /* syslog(LOG_DEBUG, "REPEATS: %s", ctime(&Cal->event_start)); */ + Put(WCC->disp_cal_items, + (char*) &Cal->event_start, + sizeof(Cal->event_start), + Cal, + delete_cal + ); + } + else { + delete_cal(Cal); + } - /** - * Encapsulate event into full VCALENDAR component. Clone it first, - * for two reasons: one, it's easier to just free the whole thing - * when we're done instead of unbundling, but more importantly, we - * can't encapsulate something that may already be encapsulated - * somewhere else. - */ - lprintf(9, "Encapsulating into a full VCALENDAR component\n"); - encaps = ical_encapsulate_subcomponent(icalcomponent_new_clone(vtodo)); - - /* Serialize it and save it to the message base */ - serv_puts("ENT0 1|||4"); - serv_getln(buf, sizeof buf); - if (buf[0] == '4') { - serv_puts("Content-type: text/calendar"); - serv_puts(""); - serv_puts(icalcomponent_as_ical_string(encaps)); - serv_puts("000"); - - /** - * Probably not necessary; the server will see the UID - * of the object and delete the old one anyway, but - * just in case... - */ - delete_existing = 1; + /* If an upper bound is set, stop when we go out of scope */ + if (final_recurrence > calv->upper_bound) stop_rr = 1; } - icalcomponent_free(encaps); } + icalrecur_iterator_free(ritr); + /* syslog(9, "Performed %d recurrences; final one is %s", num_recur, ctime(&final_recurrence)); */ +} - /** - * If the user clicked 'Delete' then explicitly delete the message. - */ - if (havebstr("delete_button")) { - delete_existing = 1; - } - if ( (delete_existing) && (msgnum > 0L) ) { - serv_printf("DELE %ld", lbstr("msgnum")); - serv_getln(buf, sizeof buf); - } - if (created_new_vtodo) { - icalcomponent_free(vtodo); - } - /** Go back to the task list */ - readloop("readfwd"); -} +void process_ical_object(long msgnum, int unread, + char *from, + char *FlatIcal, + icalcomponent_kind which_kind, + IcalCallbackFunc CallBack, + calview *calv + ) +{ + icalcomponent *cal, *c; -/** - * \brief generic item handler - * Code common to all display handlers. Given a message number and a MIME + cal = icalcomponent_new_from_string(FlatIcal); + if (cal != NULL) { + + /* A which_kind of (-1) means just load the whole thing */ + if (which_kind == (-1)) { + CallBack(cal, msgnum, from, unread, calv); + } + + /* Otherwise recurse and hunt */ + else { + + /* Simple components of desired type */ + if (icalcomponent_isa(cal) == which_kind) { + CallBack(cal, msgnum, from, unread, calv); + } + + /* Subcomponents of desired type */ + for (c = icalcomponent_get_first_component(cal, which_kind); + (c != 0); + c = icalcomponent_get_next_component(cal, which_kind)) { + CallBack(c, msgnum, from, unread, calv); + } + + } + + icalcomponent_free(cal); + } +} + +/* + * Code common to all icalendar display handlers. Given a message number and a MIME * type, we load the message and hunt for that MIME type. If found, we load * the relevant part, deserialize it into a libical component, filter it for * the requested object type, and feed it to the specified handler. - * \param mimetype mimetyp of our object - * \param which_kind sort of ical type - * \param msgnum number of the mesage in our db - * \param callback a funcion \todo - * */ -void display_using_handler(long msgnum, int unread, - icalcomponent_kind which_kind, - void (*callback)(icalcomponent *, long, char*, int) - ) { - char buf[1024]; +void load_ical_object(long msgnum, int unread, + icalcomponent_kind which_kind, + IcalCallbackFunc CallBack, + calview *calv, + int RenderAsync + ) +{ + StrBuf *Buf; + StrBuf *Data = NULL; + const char *bptr; + int Done = 0; char from[128] = ""; char mime_partnum[256]; char mime_filename[256]; char mime_content_type[256]; char mime_disposition[256]; - int mime_length; char relevant_partnum[256]; char *relevant_source = NULL; - icalcomponent *cal, *c; + int phase = 0; /* 0 = citadel headers, 1 = mime headers, 2 = body */ + char msg4_content_type[256] = ""; + char msg4_content_encoding[256] = ""; + int msg4_content_length = 0; relevant_partnum[0] = '\0'; - sprintf(buf, "MSG4 %ld", msgnum); /* we need the mime headers */ - serv_puts(buf); - serv_getln(buf, sizeof buf); - if (buf[0] != '1') return; - - while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) { - if (!strncasecmp(buf, "part=", 5)) { - extract_token(mime_filename, &buf[5], 1, '|', sizeof mime_filename); - extract_token(mime_partnum, &buf[5], 2, '|', sizeof mime_partnum); - extract_token(mime_disposition, &buf[5], 3, '|', sizeof mime_disposition); - extract_token(mime_content_type, &buf[5], 4, '|', sizeof mime_content_type); - mime_length = extract_int(&buf[5], 5); - - if ( (!strcasecmp(mime_content_type, "text/calendar")) - || (!strcasecmp(mime_content_type, "application/ics")) - || (!strcasecmp(mime_content_type, "text/vtodo")) - ) { - strcpy(relevant_partnum, mime_partnum); - } - } - else if (!strncasecmp(buf, "from=", 4)) { - extract_token(from, buf, 1, '=', sizeof(from)); - } + serv_printf("MSG4 %ld", msgnum); /* we need the mime headers */ + Buf = NewStrBuf(); + StrBuf_ServGetln(Buf); + if (GetServerStatus(Buf, NULL) != 1) { + FreeStrBuf (&Buf); + return; } - - if (!IsEmptyStr(relevant_partnum)) { - relevant_source = load_mimepart(msgnum, relevant_partnum); - if (relevant_source != NULL) { - - cal = icalcomponent_new_from_string(relevant_source); - if (cal != NULL) { - - ical_dezonify(cal); - - /** Simple components of desired type */ - if (icalcomponent_isa(cal) == which_kind) { - callback(cal, msgnum, from, unread); + while (!Done && (StrBuf_ServGetln(Buf)>=0)) { + if ( (StrLength(Buf)==3) && + !strcmp(ChrPtr(Buf), "000")) { + Done = 1; + break; + } + bptr = ChrPtr(Buf); + switch (phase) { + case 0: + if (!strncasecmp(bptr, "part=", 5)) { + extract_token(mime_filename, &bptr[5], 1, '|', sizeof mime_filename); + extract_token(mime_partnum, &bptr[5], 2, '|', sizeof mime_partnum); + extract_token(mime_disposition, &bptr[5], 3, '|', sizeof mime_disposition); + extract_token(mime_content_type, &bptr[5], 4, '|', sizeof mime_content_type); + /* do we care? mime_length = */extract_int(&bptr[5], 5); + + if ( (!strcasecmp(mime_content_type, "text/calendar")) + || (!strcasecmp(mime_content_type, "application/ics")) + || (!strcasecmp(mime_content_type, "text/vtodo")) + || (!strcasecmp(mime_content_type, "text/todo")) + ) { + strcpy(relevant_partnum, mime_partnum); } - - /** Subcomponents of desired type */ - for (c = icalcomponent_get_first_component(cal, - which_kind); - (c != 0); - c = icalcomponent_get_next_component(cal, - which_kind)) { - callback(c, msgnum, from, unread); + } + else if (!strncasecmp(bptr, "from=", 4)) { + extract_token(from, bptr, 1, '=', sizeof(from)); + } + else if ((phase == 0) && (!strncasecmp(bptr, "text", 4))) { + phase = 1; + } + break; + case 1: + if (!IsEmptyStr(bptr)) { + if (!strncasecmp(bptr, "Content-type: ", 14)) { + safestrncpy(msg4_content_type, &bptr[14], sizeof msg4_content_type); + striplt(msg4_content_type); + } + else if (!strncasecmp(bptr, "Content-transfer-encoding: ", 27)) { + safestrncpy(msg4_content_encoding, &bptr[27], sizeof msg4_content_encoding); + striplt(msg4_content_type); + } + else if ((!strncasecmp(bptr, "Content-length: ", 16))) { + msg4_content_length = atoi(&bptr[16]); + } + break; + } + else { + phase++; + + if ((msg4_content_length > 0) + && ( !strcasecmp(msg4_content_encoding, "7bit")) + && ((!strcasecmp(mime_content_type, "text/calendar")) + || (!strcasecmp(mime_content_type, "application/ics")) + || (!strcasecmp(mime_content_type, "text/vtodo")) + || (!strcasecmp(mime_content_type, "text/todo")) + ) + ) + { } - icalcomponent_free(cal); } - free(relevant_source); + case 2: + if (Data == NULL) + Data = NewStrBufPlain(NULL, msg4_content_length * 2); + if (msg4_content_length > 0) { + StrBuf_ServGetBLOBBuffered(Data, msg4_content_length); + phase ++; + } + else { + StrBufAppendBuf(Data, Buf, 0); + StrBufAppendBufPlain(Data, "\r\n", 1, 0); + } + case 3: + StrBufAppendBuf(Data, Buf, 0); } } - icalmemory_free_ring(); -} - -/** - * \brief display whole calendar - * \param msgnum number of the mesage in our db - */ -void display_calendar(long msgnum, int unread) { - display_using_handler(msgnum, unread, - ICAL_VEVENT_COMPONENT, - display_individual_cal); -} - -/** - * \brief display whole taksview - * \param msgnum number of the mesage in our db - */ -void display_task(long msgnum, int unread) { - display_using_handler(msgnum, unread, - ICAL_VTODO_COMPONENT, - display_individual_cal); -} - -/** - * \brief display the editor component for a task - */ -void display_edit_task(void) { - long msgnum = 0L; + FreeStrBuf(&Buf); - /** Force change the room if we have to */ - if (havebstr("taskrm")) { - gotoroom(bstr("taskrm")); + /* If MSG4 didn't give us the part we wanted, but we know that we can find it + * as one of the other MIME parts, attempt to load it now. + */ + if ((Data == NULL) && (!IsEmptyStr(relevant_partnum))) { + Data = load_mimepart(msgnum, relevant_partnum); } - msgnum = lbstr("msgnum"); - if (msgnum > 0L) { - /** existing task */ - display_using_handler(msgnum, 0, - ICAL_VTODO_COMPONENT, - display_edit_individual_task); - } - else { - /** new task */ - display_edit_individual_task(NULL, 0L, "", 0); + if (Data != NULL) { + relevant_source = (char*) ChrPtr(Data); + process_ical_object(msgnum, unread, + from, + relevant_source, + which_kind, + CallBack, + calv); } + FreeStrBuf (&Data); + + icalmemory_free_ring(); } -/** - *\brief save an edited task +/* + * Display a calendar item */ -void save_task(void) { - long msgnum = 0L; - - msgnum = lbstr("msgnum"); - if (msgnum > 0L) { - display_using_handler(msgnum, 0, - ICAL_VTODO_COMPONENT, - save_individual_task); - } - else { - save_individual_task(NULL, 0L, "", 0); - } +int calendar_LoadMsgFromServer(SharedMessageStatus *Stat, + void **ViewSpecific, + message_summary* Msg, + int is_new, + int i) +{ + calview *c = (calview*) *ViewSpecific; + load_ical_object(Msg->msgnum, is_new, (-1), display_individual_cal, c, 1); + return 0; } -/** - * \brief display the editor component for an event +/* + * display the editor component for an event */ void display_edit_event(void) { long msgnum = 0L; @@ -855,18 +809,16 @@ void display_edit_event(void) { msgnum = lbstr("msgnum"); if (msgnum > 0L) { /* existing event */ - display_using_handler(msgnum, 0, - ICAL_VEVENT_COMPONENT, - display_edit_individual_event); + load_ical_object(msgnum, 0, ICAL_VEVENT_COMPONENT, display_edit_individual_event, NULL, 0); } else { /* new event */ - display_edit_individual_event(NULL, 0L, "", 0); + display_edit_individual_event(NULL, 0L, "", 0, NULL); } } -/** - * \brief save an edited event +/* + * save an edited event */ void save_event(void) { long msgnum = 0L; @@ -874,12 +826,10 @@ void save_event(void) { msgnum = lbstr("msgnum"); if (msgnum > 0L) { - display_using_handler(msgnum, 0, - ICAL_VEVENT_COMPONENT, - save_individual_event); + load_ical_object(msgnum, 0, (-1), save_individual_event, NULL, 0); } else { - save_individual_event(NULL, 0L, "", 0); + save_individual_event(NULL, 0L, "", 0, NULL); } } @@ -887,17 +837,18 @@ void save_event(void) { -/** - * \brief freebusy display (for client software) - * \param req dunno. ????? +/* + * Anonymous request of freebusy data for a user */ -void do_freebusy(char *req) { +void do_freebusy(void) +{ + const char *req = ChrPtr(WC->Hdr->HR.ReqLine); char who[SIZ]; char buf[SIZ]; - char *fb; int len; + long lines; - extract_token(who, req, 1, ' ', sizeof who); + extract_token(who, req, 0, ' ', sizeof who); if (!strncasecmp(who, "/freebusy/", 10)) { strcpy(who, &who[10]); } @@ -905,27 +856,90 @@ void do_freebusy(char *req) { len = strlen(who); if ( (!strcasecmp(&who[len-4], ".vcf")) - || (!strcasecmp(&who[len-4], ".ifb")) - || (!strcasecmp(&who[len-4], ".vfb")) ) { + || (!strcasecmp(&who[len-4], ".ifb")) + || (!strcasecmp(&who[len-4], ".vfb")) ) { who[len-4] = 0; } - lprintf(9, "freebusy requested for <%s>\n", who); + syslog(9, "freebusy requested for <%s>\n", who); serv_printf("ICAL freebusy|%s", who); serv_getln(buf, sizeof buf); if (buf[0] != '1') { - wprintf("HTTP/1.1 404 %s\n", &buf[4]); + hprintf("HTTP/1.1 404 %s\n", &buf[4]); output_headers(0, 0, 0, 0, 0, 0); - wprintf("Content-Type: text/plain\r\n"); - wprintf("\r\n"); - wprintf("%s\n", &buf[4]); + hprintf("Content-Type: text/plain\r\n"); + wc_printf("%s\n", &buf[4]); + end_burst(); return; } - fb = read_server_text(); - http_transmit_thing(fb, strlen(fb), "text/calendar", 0); - free(fb); + read_server_text(WC->WBuf, &lines); + http_transmit_thing("text/calendar", 0); } + +int calendar_Cleanup(void **ViewSpecific) +{ + calview *c; + + c = (calview *) *ViewSpecific; + + wDumpContent(1); + free (c); + *ViewSpecific = NULL; + + return 0; +} + +int __calendar_Cleanup(void **ViewSpecific) +{ + calview *c; + + c = (calview *) *ViewSpecific; + + free (c); + *ViewSpecific = NULL; + + return 0; +} + + +void +InitModule_CALENDAR +(void) +{ + RegisterReadLoopHandlerset( + VIEW_CALENDAR, + calendar_GetParamsGetServerCall, + NULL, + NULL, + NULL, + calendar_LoadMsgFromServer, + calendar_RenderView_or_Tail, + calendar_Cleanup); + + RegisterReadLoopHandlerset( + VIEW_CALBRIEF, + calendar_GetParamsGetServerCall, + NULL, + NULL, + NULL, + calendar_LoadMsgFromServer, + calendar_RenderView_or_Tail, + calendar_Cleanup); + + + + RegisterPreference("daystart", _("Calendar day view begins at:"), PRF_INT, NULL); + RegisterPreference("dayend", _("Calendar day view ends at:"), PRF_INT, NULL); + RegisterPreference("weekstart", _("Week starts on:"), PRF_INT, NULL); + + WebcitAddUrlHandler(HKEY("freebusy"), "", 0, do_freebusy, COOKIEUNNEEDED|ANONYMOUS|FORCE_SESSIONCLOSE); + WebcitAddUrlHandler(HKEY("display_edit_task"), "", 0, display_edit_task, 0); + WebcitAddUrlHandler(HKEY("display_edit_event"), "", 0, display_edit_event, 0); + WebcitAddUrlHandler(HKEY("save_event"), "", 0, save_event, 0); + WebcitAddUrlHandler(HKEY("respond_to_request"), "", 0, respond_to_request, 0); + WebcitAddUrlHandler(HKEY("handle_rsvp"), "", 0, handle_rsvp, 0); +}