X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=webcit%2Fcalendar.c;h=06654213315d2a8d8e40e3e5000f0e8392980b54;hb=aa8ca3b0af3efdabd8559b886efb3164319bdce1;hp=90c3586a3f45bf4905855e59edf114628d49526e;hpb=93da51308a5937c00411fd027c35eea243ae2f17;p=citadel.git diff --git a/webcit/calendar.c b/webcit/calendar.c index 90c3586a3..066542133 100644 --- a/webcit/calendar.c +++ b/webcit/calendar.c @@ -5,29 +5,10 @@ * */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include "webcit.h" #include "webserver.h" -#ifndef HAVE_ICAL_H +#ifndef WEBCIT_WITH_CALENDAR_SERVICE /* * Handler stubs for builds with no calendar library available @@ -38,7 +19,7 @@ void cal_process_attachment(char *part_source, long msgnum, char *cal_partnum) { " but support for calendars is not available on this " "particular system. Please ask your system administrator to " "install a new version of the Citadel web service with " - "calendaring enabled.
\n" + "calendaring enabled.
\n" ); } @@ -48,7 +29,7 @@ void display_calendar(long msgnum) { "Cannot display calendar item. You are seeing this error " "because your WebCit service has not been installed with " "calendar support. Please contact your system administrator." - "
\n"); + "
\n"); } void display_task(long msgnum) { @@ -56,10 +37,10 @@ void display_task(long msgnum) { "Cannot display to-do item. You are seeing this error " "because your WebCit service has not been installed with " "calendar support. Please contact your system administrator." - "
\n"); + "
\n"); } -#else /* HAVE_ICAL_H */ +#else /* WEBCIT_WITH_CALENDAR_SERVICE */ /****** End of handler stubs. Everything below this line is real. ******/ @@ -70,6 +51,7 @@ void display_task(long msgnum) { /* * Process a calendar object * ...at this point it's already been deserialized by cal_process_attachment() + * */ void cal_process_object(icalcomponent *cal, int recursion_level, @@ -101,19 +83,28 @@ void cal_process_object(icalcomponent *cal, case ICAL_METHOD_REQUEST: wprintf("\n" "" + "SRC=\"/static/calarea_48x.gif\">" + "  " + "Meeting invitation" + "\n" + ); + break; + case ICAL_METHOD_REPLY: + wprintf("\n" + "" "  " - "Meeting invitation - \n" + "Attendee's reply to your invitation" + "\n" ); break; case ICAL_METHOD_PUBLISH: wprintf("\n" "" + "SRC=\"/static/calarea_48x.gif\">" "  " - "Published event - \n" + "Published event" + "\n" ); break; default: @@ -125,18 +116,18 @@ void cal_process_object(icalcomponent *cal, } p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY); - if (p != NULL) { + if (p != NULL) { wprintf("Summary:"); escputs((char *)icalproperty_get_comment(p)); wprintf("\n"); - } + } p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY); - if (p != NULL) { + if (p != NULL) { wprintf("Location:"); escputs((char *)icalproperty_get_comment(p)); wprintf("\n"); - } + } /* * Only show start/end times if we're actually looking at the VEVENT @@ -146,20 +137,32 @@ void cal_process_object(icalcomponent *cal, p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY); - if (p != NULL) { + if (p != NULL) { t = icalproperty_get_dtstart(p); - tt = icaltime_as_timet(t); - fmt_date(buf, tt); - wprintf("Starting date/time:" - "%s", buf - ); + + if (t.is_date) { + wprintf("Date:" + "" + "%s %d, %d", + months[t.month - 1], + t.day, t.year + ); + } + else { + tt = icaltime_as_timet(t); + fmt_date(buf, tt, 0); + wprintf("Starting date/time:" + "" + "%s", buf + ); + } } p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY); - if (p != NULL) { + if (p != NULL) { t = icalproperty_get_dtend(p); tt = icaltime_as_timet(t); - fmt_date(buf, tt); + fmt_date(buf, tt, 0); wprintf("Ending date/time:" "%s", buf ); @@ -168,11 +171,30 @@ void cal_process_object(icalcomponent *cal, } p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY); - if (p != NULL) { + if (p != NULL) { wprintf("Description:"); escputs((char *)icalproperty_get_comment(p)); wprintf("\n"); - } + } + + /* 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("Attendee:"); + strcpy(buf, icalproperty_get_attendee(p)); + if (!strncasecmp(buf, "MAILTO:", 7)) { + + /* screen name or email address */ + strcpy(buf, &buf[7]); + striplt(buf); + escputs(buf); + wprintf(" "); + + /* participant status */ + partstat_as_string(buf, p); + escputs(buf); + } + wprintf("\n"); + } /* If the component has subcomponents, recurse through them. */ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT); @@ -186,11 +208,12 @@ void cal_process_object(icalcomponent *cal, if (the_method == ICAL_METHOD_REQUEST) { /* Check for conflicts */ + lprintf(9, "Checking server calendar for conflicts...\n"); serv_printf("ICAL conflicts|%ld|%s|", msgnum, cal_partnum); - serv_gets(buf); + serv_getln(buf, sizeof buf); if (buf[0] == '1') { - while (serv_gets(buf), strcmp(buf, "000")) { - extract(conflict_name, buf, 3); + while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) { + extract_token(conflict_name, buf, 3, '|', sizeof conflict_name); is_update = extract_int(buf, 4); wprintf("%s" "" @@ -214,25 +237,46 @@ void cal_process_object(icalcomponent *cal, "\n"); } } + lprintf(9, "...done.\n"); /* Display the Accept/Decline buttons */ - wprintf("" - "
\n" - "\n" - "  " - "\n" - "  " - "\n" - "" - "" - "
" + wprintf("How would you like to respond to this invitation?" + "" + "Accept" + " | " + "Tentative" + " | " + "Decline" + "\n", + msgnum, cal_partnum, + msgnum, cal_partnum, + msgnum, cal_partnum + ); + + } + + /* If this is a REPLY, display update button */ + if (the_method == ICAL_METHOD_REPLY) { + + /*********** + * 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("" + "Click Update to accept this reply and " + "update your calendar." + "" + "Update" + " | " + "Ignore" + "" "\n", + msgnum, cal_partnum, msgnum, cal_partnum ); @@ -256,11 +300,11 @@ void cal_process_attachment(char *part_source, long msgnum, char *cal_partnum) { cal = icalcomponent_new_from_string(part_source); if (cal == NULL) { - wprintf("Error parsing calendar object: %s
\n", - icalerror_strerror(icalerrno)); + wprintf("Error parsing calendar object
\n"); return; } + ical_dezonify(cal); cal_process_object(cal, 0, msgnum, cal_partnum); /* Free the memory we obtained from libical's constructor */ @@ -276,21 +320,104 @@ void cal_process_attachment(char *part_source, long msgnum, char *cal_partnum) { void respond_to_request(void) { char buf[SIZ]; - output_headers(3); + output_headers(1, 1, 2, 0, 0, 0, 0); - wprintf("
" - "Respond to meeting request" - "

\n" + wprintf("
\n"); + wprintf("
" + "Respond to meeting request" + "
\n" ); + wprintf("
\n
\n"); serv_printf("ICAL respond|%s|%s|%s|", bstr("msgnum"), bstr("cal_partnum"), bstr("sc") ); - serv_gets(buf); - escputs(buf); + serv_getln(buf, sizeof buf); + + if (buf[0] == '2') { + wprintf("
" + "" + "" + ); + if (!strcasecmp(bstr("sc"), "accept")) { + wprintf("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. " + "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, " + ); + } + wprintf("and a reply has been sent to the meeting organizer." + "
\n" + ); + } else { + wprintf("" + "%s\n", &buf[4]); + } + + wprintf("wc_roomname); + wprintf("\">
Return to messages

\n"); + + wDumpContent(1); +} + + + +/* + * Handle an incoming RSVP + */ +void handle_rsvp(void) { + char buf[SIZ]; + + output_headers(1, 1, 2, 0, 0, 0, 0); + + wprintf("
\n"); + wprintf("
" + "" + "Update your calendar with this RSVP" + "
\n" + "
\n
\n" + ); + + serv_printf("ICAL handle_rsvp|%s|%s|%s|", + bstr("msgnum"), + bstr("cal_partnum"), + bstr("sc") + ); + serv_getln(buf, sizeof buf); + + if (buf[0] == '2') { + wprintf("
" + "" + "" + ); + if (!strcasecmp(bstr("sc"), "update")) { + wprintf("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." + ); + } + wprintf("
\n" + ); + } else { + wprintf("" + "%s\n", &buf[4]); + } + + wprintf("wc_roomname); + wprintf("\">
Return to messages

\n"); wDumpContent(1); } @@ -317,33 +444,17 @@ void display_individual_cal(icalcomponent *cal, long msgnum) { WC->num_cal += 1; WC->disp_cal = realloc(WC->disp_cal, - (sizeof(icalcomponent *) * WC->num_cal) ); - WC->disp_cal[WC->num_cal - 1] = icalcomponent_new_clone(cal); + (sizeof(struct disp_cal) * WC->num_cal) ); + WC->disp_cal[WC->num_cal - 1].cal = icalcomponent_new_clone(cal); - WC->cal_msgnum = realloc(WC->cal_msgnum, - (sizeof(long) * WC->num_cal) ); - WC->cal_msgnum[WC->num_cal - 1] = msgnum; + WC->disp_cal[WC->num_cal - 1].cal_msgnum = msgnum; } -/* - * Display a task in the task list - */ -void display_individual_task(icalcomponent *vtodo, long msgnum) { - icalproperty *p; - - p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY); - wprintf("
  • ", msgnum); - if (p != NULL) { - escputs((char *)icalproperty_get_comment(p)); - } - wprintf("\n"); -} - - /* * Display a task by itself (for editing) + * */ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum) { icalcomponent *vtodo; @@ -356,33 +467,56 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum) { 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 + ); + return; + } } else { vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT); created_new_vtodo = 1; } - output_headers(3); - wprintf("
    " - "Edit task" - "

    \n" + output_headers(1, 1, 2, 0, 0, 0, 0); + wprintf("
    \n" + "" + "" + "
    Edit task" + "
    \n" + "
    \n
    \n" ); + wprintf("
    " + "
    "); + wprintf("
    \n"); wprintf("\n", msgnum); - wprintf("Summary: " + wprintf("\n"); + + wprintf("\n"); - wprintf("Start date: "); + wprintf("\n"); - wprintf("Due date: "); + wprintf("
    Summary:" "
    \n"); + wprintf("\">
    Start date:"); p = icalcomponent_get_first_property(vtodo, ICAL_DTSTART_PROPERTY); if (p != NULL) { t = icalproperty_get_dtstart(p); @@ -391,9 +525,9 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum) { t = icaltime_from_timet(now, 0); } display_icaltimetype_as_webform(&t, "dtstart"); - wprintf("
    \n"); + wprintf("
    Due date:"); p = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY); if (p != NULL) { t = icalproperty_get_due(p); @@ -402,18 +536,19 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum) { t = icaltime_from_timet(now, 0); } display_icaltimetype_as_webform(&t, "due"); - wprintf("
    \n"); - - wprintf("

    \n"); + wprintf("
    \n"); - wprintf("" + wprintf("
    " + "" "  " "\n" "  " @@ -423,6 +558,7 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum) { wprintf("\n"); + wprintf("
    \n"); wDumpContent(1); if (created_new_vtodo) { @@ -432,16 +568,35 @@ void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum) { /* * Save an edited task + * */ void save_individual_task(icalcomponent *supplied_vtodo, long msgnum) { char buf[SIZ]; int delete_existing = 0; icalproperty *prop; - icalcomponent *vtodo; + icalcomponent *vtodo, *encaps; int created_new_vtodo = 0; + int i; + int sequence = 0; + struct icaltimetype t; 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 + ); + return; + } } else { vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT); @@ -455,6 +610,7 @@ void save_individual_task(icalcomponent *supplied_vtodo, long msgnum) { while (prop = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY), prop != NULL) { icalcomponent_remove_property(vtodo, prop); + icalproperty_free(prop); } icalcomponent_add_property(vtodo, icalproperty_new_summary(bstr("summary"))); @@ -462,6 +618,7 @@ void save_individual_task(icalcomponent *supplied_vtodo, long msgnum) { 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"))); @@ -469,30 +626,66 @@ void save_individual_task(icalcomponent *supplied_vtodo, long msgnum) { 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( - icaltime_from_webform("dtstart") - ) + 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( - icaltime_from_webform("due") - ) + 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) + ); + } + + /* 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) + ); + + /* + * 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 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_gets(buf); + serv_getln(buf, sizeof buf); if (buf[0] == '4') { serv_puts("Content-type: text/calendar"); serv_puts(""); - serv_puts(icalcomponent_as_ical_string(vtodo)); + serv_puts(icalcomponent_as_ical_string(encaps)); serv_puts("000"); /* Probably not necessary; the server will see the UID @@ -501,6 +694,7 @@ void save_individual_task(icalcomponent *supplied_vtodo, long msgnum) { */ delete_existing = 1; } + icalcomponent_free(encaps); } /* @@ -512,7 +706,7 @@ void save_individual_task(icalcomponent *supplied_vtodo, long msgnum) { if ( (delete_existing) && (msgnum > 0L) ) { serv_printf("DELE %ld", atol(bstr("msgnum"))); - serv_gets(buf); + serv_getln(buf, sizeof buf); } if (created_new_vtodo) { @@ -530,6 +724,7 @@ void save_individual_task(icalcomponent *supplied_vtodo, long msgnum) { * 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. + * */ void display_using_handler(long msgnum, char *mimetype, @@ -548,15 +743,15 @@ void display_using_handler(long msgnum, sprintf(buf, "MSG0 %ld|1", msgnum); /* ask for headers only */ serv_puts(buf); - serv_gets(buf); + serv_getln(buf, sizeof buf); if (buf[0] != '1') return; - while (serv_gets(buf), strcmp(buf, "000")) { + while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) { if (!strncasecmp(buf, "part=", 5)) { - extract(mime_filename, &buf[5], 1); - extract(mime_partnum, &buf[5], 2); - extract(mime_disposition, &buf[5], 3); - extract(mime_content_type, &buf[5], 4); + 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")) { @@ -573,6 +768,8 @@ void display_using_handler(long msgnum, 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); @@ -603,12 +800,17 @@ void display_calendar(long msgnum) { void display_task(long msgnum) { display_using_handler(msgnum, "text/calendar", ICAL_VTODO_COMPONENT, - display_individual_task); + display_individual_cal); } void display_edit_task(void) { long msgnum = 0L; + /* Force change the room if we have to */ + if (strlen(bstr("taskrm")) > 0) { + gotoroom(bstr("taskrm")); + } + msgnum = atol(bstr("msgnum")); if (msgnum > 0L) { /* existing task */ @@ -667,4 +869,48 @@ void save_event(void) { } } -#endif /* HAVE_ICAL_H */ + + + + +/* + * freebusy display (for client software) + */ +void do_freebusy(char *req) { + char who[SIZ]; + char buf[SIZ]; + char *fb; + + extract_token(who, req, 1, ' ', sizeof who); + if (!strncasecmp(who, "/freebusy/", 10)) { + strcpy(who, &who[10]); + } + unescape_input(who); + + if ( (!strcasecmp(&who[strlen(who)-4], ".vcf")) + || (!strcasecmp(&who[strlen(who)-4], ".ifb")) + || (!strcasecmp(&who[strlen(who)-4], ".vfb")) ) { + who[strlen(who)-4] = 0; + } + + lprintf(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.0 404 %s\n", &buf[4]); + output_headers(0, 0, 0, 0, 0, 0, 0); + wprintf("Content-Type: text/plain\r\n"); + wprintf("\r\n"); + wprintf("%s\n", &buf[4]); + return; + } + + fb = read_server_text(); + http_transmit_thing(fb, strlen(fb), "text/calendar", 0); + free(fb); +} + + + +#endif /* WEBCIT_WITH_CALENDAR_SERVICE */